Accessibility in Flutter: what every developer should know
By Ann Tech · 15 March 2026
Accessibility (a11y) means your app works for users who rely on screen readers, large text, high contrast, and keyboard navigation. Flutter provides the tools — you have to use them correctly.
How Flutter handles accessibility
Flutter maintains a semantics tree alongside the widget tree. Screen readers (TalkBack on Android, VoiceOver on iOS) read from this tree, not from visual pixels. Most Material widgets populate the semantics tree automatically. Custom widgets need explicit semantics.
Adding semantics to custom widgets
// Decorative image — exclude from semantics
ExcludeSemantics(
child: Image.asset('assets/background.png'),
)
// Meaningful image — label it
Semantics(
label: 'Product image: Red running shoes',
image: true,
child: CachedNetworkImage(imageUrl: product.imageUrl),
)
// Custom interactive element
Semantics(
label: 'Add ${product.name} to cart',
button: true,
child: GestureDetector(
onTap: () => cart.addItem(product),
child: const Icon(Icons.add_shopping_cart),
),
)
MergeSemantics for grouped content
// Without MergeSemantics: screen reader announces each child separately
// With MergeSemantics: announces as one unit — "4.5, 128 reviews"
MergeSemantics(
child: Row(
children: [
const Icon(Icons.star, color: Colors.amber),
Text('${product.rating}'),
Text('(${product.reviewCount} reviews)'),
],
),
)
Touch targets
Apple HIG and Material guidelines both require 44×44pt minimum touch targets. Widgets smaller than this are difficult to tap accurately:
// Expand hit area without changing visual size
GestureDetector(
behavior: HitTestBehavior.opaque,
onTap: onPressed,
child: Padding(
padding: const EdgeInsets.all(12), // Expands tappable area
child: const Icon(Icons.close, size: 20),
),
)
Dynamic text sizing
// Always use maxLines + overflow for text that might scale with user settings
Text(
product.name,
style: Theme.of(context).textTheme.titleMedium,
maxLines: 2,
overflow: TextOverflow.ellipsis,
)
Automated accessibility checks in tests
testWidgets('meets accessibility guidelines', (tester) async {
final handle = tester.ensureSemantics();
await tester.pumpWidget(MaterialApp(home: ProductCard(product: testProduct)));
await expectLater(tester, meetsGuideline(androidTapTargetGuideline));
await expectLater(tester, meetsGuideline(iOSTapTargetGuideline));
await expectLater(tester, meetsGuideline(labeledTapTargetGuideline));
await expectLater(tester, meetsGuideline(textContrastGuideline));
handle.dispose();
});
Manual testing checklist
- Enable TalkBack (Android) / VoiceOver (iOS) and navigate through your app using swipe gestures
- Set system font to the largest available size and check every screen for overflow or clipping
- Enable high contrast mode and verify all text remains readable against its background
- Check that every interactive element announces a meaningful label — not just "Button"
Common pitfalls
Icon-only buttons with no label. An IconButton with only icon: Icon(Icons.delete) announces "Button" with no context to screen reader users. Always provide a tooltip or wrap in Semantics(label: 'Delete order').
Custom gestures without accessible fallbacks. Swipe-to-delete is invisible to screen reader users who navigate by swiping. Provide a visible delete button or long-press menu as an alternative.
Not testing at large font sizes. Enable "Larger Accessibility Sizes" on iOS and "Largest" font size on Android before submitting. Cards overflow, buttons clip, and labels truncate in ways only visible at extreme text scales.
Sign in to like, dislike, or report.