How to use
- Copy the installation command and review the source package before installing it in your own agent environment.
- Check the source document for required tools, credentials and supported hosts.
- Describe your input and desired deliverable; review the output against the source material.
npx skills add https://github.com/thesimpleapp/flutter-app-skills --skill flutter-improving-accessibilityBefore you start
- Installation does not include a model subscription, API allowance or permission to access your files.
- Repository stars and installation counts describe activity, not an independent quality or security test.
Rendered from the source repository. Headings, examples, code, tables, links, and referenced images are preserved.
Flutter accessibility
Prefer built-in Material and Cupertino widgets — they already emit a TalkBack / VoiceOver / JAWS / NVDA tree. Add Semantics (or Semantics.fromProperties) only on custom painted or gestured widgets.
If this repo already has a Semantics helper, a11y test harness, labeled-control convention, or a pressable with semanticLabel, extend that. Do not wrap it in a second Semantics(label:) that repeats visible text.
Official: Accessibility, assistive technologies, UI design and styling, accessibility testing.
References
Read references/recipes.md when implementing a custom control, merge/exclude/block, MediaQuery wiring, or the guideline test.
Detect first
Search the repo for Semantics(, MergeSemantics, ExcludeSemantics, BlockSemantics, semanticLabel, excludeFromSemantics, tooltip:, meetsGuideline(, ensureSemantics(, highContrastTheme, accessibleNavigationOf, textScalerOf. Open a nearby production screen and copy that pattern.
Hard rules
Semantics
- Custom paint /
GestureDetector/InkWell: setlabel, a role flag (button,slider,textField),tooltip/hint, andonTapso the node is announced as a control. - Role is a flag:
Semantics(button: true, label: 'Submit'). Never put "button" in the label.label/hint/tooltiprequire an ambientDirectionalityor an explicittextDirection. Transient status:liveRegion: true. container: trueintroduces aSemanticsNode— use it for a distinct TalkBack/VoiceOver focus stop.
Merge, exclude, block
- One control + its visible
Text: wrap both inMergeSemantics. Never merge two checkboxes, two tappable children, or conflictingselected/checkedvalues. ExcludeSemanticsonly for decorative or redundant descendants.Semantics(excludeSemantics: true)replaces all child semantics with this node. Hide widgets painted behind a modal orDrawerwithBlockSemanticson the overlay, notExcludeSemanticson the overlay child.
Hit targets and names
- Every tappable target ≥ 48×48 logical pixels (
kMinInteractiveDimension). Cupertino floor iskMinInteractiveDimensionCupertino(44.0). PreferIconButtonso the hit region stays 48 regardless oficonSize. Do not passIconButton.constraintsor compactVisualDensitythat shrinks below 48. - Icon-only
IconButton/ FAB:tooltipis a short action name. Custom icon taps:Semantics(label: ..., button: true). DecorativeIcon:semanticLabel. MeaningfulImage:semanticLabel. DecorativeImage:excludeFromSemantics: true.
Text scale
- Read with
MediaQuery.textScalerOf(context)and compute sizes withtextScaler.scale(fontSize). NevertextScaleFactor— deprecated and cannot represent Android 14 nonlinear scaling. Textalready honors the OS font-size setting. Test every screen on a small device at the largest OS text size.MediaQuery.withNoTextScalingonly to stop icon fonts from scaling. Never disable scaling at theMaterialApproot.
MediaQuery flags
- Call
MediaQuery.accessibleNavigationOf,highContrastOf,disableAnimationsOf, andtextScalerOf— notMediaQuery.of. - When
accessibleNavigationOfis true, disable timeouts or raise their minimum duration. - When
highContrastOfis true, setMaterialApp.highContrastThemeandhighContrastDarkTheme. Wrapping a subtree inMediaQuery(data: copyWith(highContrast: true))does not switch those themes. disableAnimationsOfis Android Remove animations.AnimationControlleralready honorsSemanticsBinding.disableAnimations. Custom explicit animations must still readdisableAnimationsOf. iOS Reduce Motion isAccessibilityFeatures.reduceMotionand does not setdisableAnimations.
Traversal
- TalkBack/VoiceOver sibling order:
Semantics(sortKey: OrdinalSortKey(n)). Keyboard/Tab order is a different tree:FocusTraversalGroup/FocusTraversalOrder. Do not usesortKeyto fix Tab order.
Contrast
- At least 4.5:1 between text/controls and background (except disabled). 3.0:1 for large text (18pt+ regular or 14pt+ bold). Do not convey state by color alone.
Tests and devices
tester.ensureSemantics(), thenexpectLater(tester, meetsGuideline(...)), thenhandle.dispose(). Guidelines:androidTapTargetGuideline,iOSTapTargetGuideline,labeledTapTargetGuideline,textContrastGuideline.- Walk every changed screen with TalkBack and VoiceOver. Flutter web:
SemanticsBinding.instance.ensureSemantics()inmain(). Debug withshowSemanticsDebuggerorflutter run -d chrome --profile --dart-define=FLUTTER_WEB_DEBUG_SHOW_SEMANTICS=true.
Samples
Semantics(
button: true,
label: 'Submit',
hint: 'Sends the form',
onTap: onSubmit,
child: CustomPaint(painter: SubmitPainter()),
)
IconButton(
tooltip: 'Search',
icon: const Icon(Icons.search),
onPressed: onSearch,
)
final TextScaler scaler = MediaQuery.textScalerOf(context);
final bool screenReader = MediaQuery.accessibleNavigationOf(context);Do not
- Wrap an already-semantic Material/Cupertino widget in
Semantics(label:)that repeats visible text. - Put
ExcludeSemanticsaround interactive content, or use it instead ofBlockSemanticsto hide widgets behind a dialog. MergeSemanticsover two checkboxes or two tappable children.- Icon-only control with no
tooltip, noSemantics.label, and noIcon.semanticLabel. MediaQuery.of(context).textScaleFactororText(textScaleFactor:).- Disable text scaling at the app root, or shrink
IconButtonbelow 48. - Assume
MediaQuery(data: copyWith(disableAnimations: true))stopsAnimationController. - Write the role into the label (
label: 'Submit button'). - Use
Semantics.sortKeyto fix keyboard Tab order.
Fix a screen
- Detect the existing a11y convention and reuse it.
- Leave stock Material/Cupertino widgets unlabeled unless they are custom-painted or icon-only.
- Give every icon-only control a
tooltiporSemantics(label:, button: true). Pad the hit target to 48, not the glyph. - Read
MediaQuery.*Of. Honor largest text, high-contrast themes, screen-reader timeouts, and reduced motion. - Add the guideline test. Walk TalkBack and VoiceOver.
Verify
tester.ensureSemantics()+ the fourmeetsGuidelinechecks +handle.dispose()on the screen you touched.- Layout still fits at the OS largest text size on a small device.
- Contrast ≥ 4.5:1 (3:1 large text); state is not color-only.
- TalkBack and VoiceOver: every control has an intelligible name and the right role.
- Web: semantics enabled from
main().



