👆 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 GestureDetector or InkWell widgets

WCAG Guideline

This guideline is based on two related criteria:

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…


This site uses Just the Docs, a documentation theme for Jekyll.