👆 Touch targets
Common mistake
A common mistake is making interactive elements, especially icon buttons, too small to tap reliably. Small touch targets are problematic for users with motor disabilities, tremors, or large fingers, leading to missed taps and accidental activation of adjacent elements.
🎯 Relevant elements
This guideline applies to every tappable element in the UI:
- Icon buttons and circular action buttons
- Navigation bar items
- Checkboxes, radio buttons, and toggles
- List items and card tap areas
- Custom
GestureDetectororInkWellwidgets
WCAG Guideline
This guideline is based on two related criteria:
- WCAG 2.2 - 2.5.5 Target Size (Enhanced) (Level AAA). The touch target must be at least 44×44 CSS pixels.
- WCAG 2.2 - 2.5.8 Target Size (Minimum) (Level AA). The touch target must be at least 24×24 CSS pixels.
On mobile, CSS pixels map closely to dp (density-independent pixels). A minimum of 48×48 dp is recommended by both Google Material and Apple HIG, satisfying criterion 2.5.5.
Solution
Use ButtonStyle.minimumSize or wrap small widgets in a SizedBox / Padding to guarantee a minimum tap area. Never rely solely on the visual size of the widget.
// ❌ Don't - icon button with no minimum size; visual size may be only 24×24
IconButton(
icon: const Icon(Icons.close),
onPressed: onClose,
);
// ✅ Do - enforce a minimum touch target of 48×48 dp
IconButton(
icon: const Icon(Icons.close),
onPressed: onClose,
style: IconButton.styleFrom(
minimumSize: const Size(48, 48),
),
);
For custom tappable widgets, expand the hit area with Padding or SizedBox without changing the visual size:
// ✅ Do - expand hit area while keeping the visual icon size
GestureDetector(
onTap: onClose,
behavior: HitTestBehavior.opaque,
child: const Padding(
padding: EdgeInsets.all(12), // adds 12 dp on each side to a 24 dp icon → 48 dp total
child: Icon(Icons.close, size: 24),
),
);
For circular buttons (e.g. back buttons or floating action buttons), set minimumSize on the ButtonStyle:
// ✅ Do - circular button with a guaranteed 48×48 dp touch target
ElevatedButton(
style: ElevatedButton.styleFrom(
shape: const CircleBorder(),
minimumSize: const Size(48, 48),
padding: EdgeInsets.zero,
),
onPressed: onBack,
child: const Icon(Icons.arrow_back),
);
Validation & Testing
See the Validation & Testing setup guide for tool configuration.
Manual review
Use the Flutter Inspector’s widget size overlay (Flutter DevTools → Widget Inspector) to verify the rendered size of tappable elements. All interactive widgets must have a hit area of at least 48×48 dp.
accessibility_tools
The accessibility_tools package flags touch targets that are smaller than the platform minimum at runtime during development.
TalkBack & VoiceOver
Attempt to activate each interactive element using a single tap. If elements are frequently missed or require multiple attempts, the touch target is too small.
flutter_test
testWidgets('close button meets 48×48 dp minimum touch target', (tester) async {
await tester.pumpWidget(MaterialApp(home: Scaffold(body: MyScreen())));
final closeButton = tester.getRect(find.byIcon(Icons.close));
expect(closeButton.width, greaterThanOrEqualTo(48));
expect(closeButton.height, greaterThanOrEqualTo(48));
});
🛠️ Usage baseflow-a11y-components library
🛠️ Work in progress…