Irbisa · Cheatsheet
Accessibility & Localization
Semantics, screen readers, text scaling, flutter_localizations, ARB, intl, plurals, RTL
What does the
Semanticswidget do, and how doMergeSemanticsandExcludeSemanticsdiffer from it?// Semantics: say what a custom widget meansSemantics( label: 'Rating', value: '$stars of 5', child: ExcludeSemantics(child: StarRow(stars: stars)), // 5 icons -> 1 node);…TalkBack announces your app-bar icon as just "button". How do you give an icon-only control a name?
// wrong: announced as "button" and nothing elseIconButton(icon: const Icon(Icons.search), onPressed: _openSearch); // right: the tooltip is read as the name; sighted users see it on long-pressIconButton( icon: const Icon(Icons.search),…How do you localize a Flutter app: what do
flutter_localizations, ARB files,gen-l10nandintleach do?// l10n.yaml: arb-dir: lib/l10n// template-arb-file: app_en.arb// lib/l10n/app_en.arb:// { "cartTitle": "Cart",// "@cartTitle": { "description": "Title of the cart screen" } }// lib/l10n/app_ru.arb: { "cartTitle": "Корзина" }…How do you make a Flutter app accessible: what do you check on every screen?
What does TalkBack announce for this row, and what changes if you delete
MergeSemantics?bool agreed = false;MergeSemantics( child: Row( children: [ Checkbox( value: agreed, onChanged: (v) => setState(() => agreed = v ?? false), ), const Text('I agree to the terms'), ], ),)A product card with a title, a price and a tap target is read as three separate stops. How do you make it one button?
// wrong: three stops - "Name", "5 EUR" and an unnamed tap regionStack(children: [ Column(children: [Text(name), Text('$price EUR')]), Positioned.fill(child: InkWell(onTap: _open)),]);…Find the bug in this custom on/off switch: it looks right and toggles on tap, yet a TalkBack user cannot tell whether it is on.
Semantics( button: true, label: label, child: GestureDetector( onTap: () => onChanged(!value), child: AnimatedContainer( duration: const Duration(milliseconds: 200), width: 64, height: 48, alignment: value ? Alignment.centerRight : Alignment.centerLeft, color: value ? Colors.green : Colors.grey, child: const CircleAvatar(radius: 20, backgroundColor: Colors.white), ), ),)A filter changes the result count but the screen reader stays silent. How do you announce a change nobody focused?
// right: status text that is re-read whenever it changesSemantics( liveRegion: true, child: Text('$count results'),);…Tab jumps around your form in an order that makes no sense, though it looks fine. What decides that order?
// wrong: two visual columns; traversal zigzags left-right-left-rightRow(children: [ Column(children: [TextField(controller: firstName), TextField(controller: lastName)]), Column(children: [TextField(controller: street), TextField(controller: city)]),]);…An audit flags your custom chip row for tap-target size and contrast but passes the Material buttons beside it. What is Material doing that you are not?
// wrong: 24 dp target, padding outside the detector, ~2:1 contrastPadding( padding: const EdgeInsets.all(12), child: GestureDetector( onTap: _remove, child: const Icon(Icons.close, color: Color(0xFFBDBDBD)),…A user turns on Reduce Motion and your onboarding still parallaxes and auto-advances. Which
MediaQueryflags exist, and what should change?This label works in English. What does it show in Russian for 1, 2, 5 and 21 items, and how do you fix it?
String itemsLabel(int n, String lang) { if (lang == 'ru') { return '$n ${n == 1 ? 'товар' : 'товаров'}'; } return '$n ${n == 1 ? 'item' : 'items'}';}A German user sees a price as $1,234.50 and a date as 12/03/2026, and an event shows the wrong hour. What did the formatting code get wrong?
import 'package:timezone/data/latest.dart' as tzdata;import 'package:timezone/timezone.dart' as tz; // wrong: pattern frozen to one country, no locale, the handset's zoneText('${DateFormat('dd/MM/yyyy HH:mm').format(start.toLocal())} ' '${NumberFormat('\$#,##0.00').format(price)}');…The app comes up in English on a Spanish phone even though
app_es.arbexists. How does Flutter pick a locale, and how do you add an in-app language switch?final localeStore = ValueNotifier<Locale?>(null); // null = follow the system Future<void> main() async { WidgetsFlutterBinding.ensureInitialized(); final prefs = await SharedPreferences.getInstance(); final code = prefs.getString('locale');…Find the bug: after the user picks German in the in-app language switch, every screen changes language except this one, until it is reopened.
class _OrdersPageState extends State<OrdersPage> { late final l10n = AppLocalizations.of(context)!; @override Widget build(BuildContext context) { return Scaffold( appBar: AppBar(title: Text(l10n.ordersTitle)), body: widget.orders.isEmpty ? Center(child: Text(l10n.noOrders)) : OrderList(orders: widget.orders), ); }}In Arabic your row's icon stays on the left and the padding sits on the wrong side. What mirrors automatically in Flutter and what never does?
// wrong: physical sides - identical in Arabic, so wrong in ArabicPadding( padding: const EdgeInsets.only(left: 16, right: 8), child: Align(alignment: Alignment.centerLeft, child: Text(name)),);…Your widget tests assert on
find.text('Save'), yet a German screen shipped untranslated. How should localized UI be tested?Widget wrap(Widget child, Locale locale) => MaterialApp( locale: locale, // pins this test's language localizationsDelegates: AppLocalizations.localizationsDelegates, supportedLocales: AppLocalizations.supportedLocales, home: child, );…The backend returns
{"code": "INSUFFICIENT_FUNDS"}and your Bloc stores the sentence it will show. What breaks, and where does translation belong?sealed class Failure { const Failure(); }class InsufficientFunds extends Failure { const InsufficientFunds(this.shortfall); final num shortfall; }class SessionExpired extends Failure { const SessionExpired(); }class UnknownFailure extends Failure { const UnknownFailure(this.code); final String code; } // Data layer: wire code -> typed failure, no display text…A PR adds an icon-only toolbar. What can a widget test prove about its accessibility, and what still needs a device with TalkBack on?
testWidgets('editor toolbar meets the accessibility guidelines', (tester) async { await tester.pumpWidget(const MaterialApp(home: EditorPage())); await expectLater(tester, meetsGuideline(androidTapTargetGuideline)); // 48 dp await expectLater(tester, meetsGuideline(iOSTapTargetGuideline)); // 44 pt await expectLater(tester, meetsGuideline(labeledTapTargetGuideline));…TalkBack skips your revenue chart completely. How do you give a
CustomPainteror a customRenderObjectreal semantics?class BarsPainter extends CustomPainter { BarsPainter(this.values, this.labels); final List<double> values; final List<String> labels; @override…You are growing an app from one language to many. What do you put in CI, and how does pseudo-localization help?