thesimpleapp/flutter-app-skills

flutter-improving-accessibility

Make Flutter UIs work with TalkBack, VoiceOver, JAWS/NVDA, large text, high contrast, and reduced motion.

Zobacz źródło

Jak używać

  1. Skopiuj polecenie instalacji i sprawdź źródła przed instalacją we własnym środowisku agenta.
  2. W oryginale sprawdź wymagane narzędzia, dane dostępowe i obsługiwane środowiska.
  3. Opisz dane wejściowe i oczekiwany rezultat; porównaj wynik ze źródłami.
npx skills add https://github.com/thesimpleapp/flutter-app-skills --skill flutter-improving-accessibility

Przed rozpoczęciem

  • Instalacja nie obejmuje subskrypcji modelu, limitu API ani uprawnień do plików.
  • Gwiazdki i instalacje pokazują aktywność, a nie niezależny test jakości lub bezpieczeństwa.
Oryginalny dokument Skill

Treść z repozytorium z zachowaniem nagłówków, przykładów, kodu, tabel, linków i obrazów.

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: set label, a role flag (button, slider, textField), tooltip / hint, and onTap so the node is announced as a control.
  • Role is a flag: Semantics(button: true, label: 'Submit'). Never put "button" in the label. label / hint / tooltip require an ambient Directionality or an explicit textDirection. Transient status: liveRegion: true.
  • container: true introduces a SemanticsNode — use it for a distinct TalkBack/VoiceOver focus stop.

Merge, exclude, block

  • One control + its visible Text: wrap both in MergeSemantics. Never merge two checkboxes, two tappable children, or conflicting selected / checked values.
  • ExcludeSemantics only for decorative or redundant descendants. Semantics(excludeSemantics: true) replaces all child semantics with this node. Hide widgets painted behind a modal or Drawer with BlockSemantics on the overlay, not ExcludeSemantics on the overlay child.

Hit targets and names

  • Every tappable target ≥ 48×48 logical pixels (kMinInteractiveDimension). Cupertino floor is kMinInteractiveDimensionCupertino (44.0). Prefer IconButton so the hit region stays 48 regardless of iconSize. Do not pass IconButton.constraints or compact VisualDensity that shrinks below 48.
  • Icon-only IconButton / FAB: tooltip is a short action name. Custom icon taps: Semantics(label: ..., button: true). Decorative Icon: semanticLabel. Meaningful Image: semanticLabel. Decorative Image: excludeFromSemantics: true.

Text scale

  • Read with MediaQuery.textScalerOf(context) and compute sizes with textScaler.scale(fontSize). Never textScaleFactor — deprecated and cannot represent Android 14 nonlinear scaling.
  • Text already honors the OS font-size setting. Test every screen on a small device at the largest OS text size. MediaQuery.withNoTextScaling only to stop icon fonts from scaling. Never disable scaling at the MaterialApp root.

MediaQuery flags

  • Call MediaQuery.accessibleNavigationOf, highContrastOf, disableAnimationsOf, and textScalerOf — not MediaQuery.of.
  • When accessibleNavigationOf is true, disable timeouts or raise their minimum duration.
  • When highContrastOf is true, set MaterialApp.highContrastTheme and highContrastDarkTheme. Wrapping a subtree in MediaQuery(data: copyWith(highContrast: true)) does not switch those themes.
  • disableAnimationsOf is Android Remove animations. AnimationController already honors SemanticsBinding.disableAnimations. Custom explicit animations must still read disableAnimationsOf. iOS Reduce Motion is AccessibilityFeatures.reduceMotion and does not set disableAnimations.

Traversal

  • TalkBack/VoiceOver sibling order: Semantics(sortKey: OrdinalSortKey(n)). Keyboard/Tab order is a different tree: FocusTraversalGroup / FocusTraversalOrder. Do not use sortKey to 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(), then expectLater(tester, meetsGuideline(...)), then handle.dispose(). Guidelines: androidTapTargetGuideline, iOSTapTargetGuideline, labeledTapTargetGuideline, textContrastGuideline.
  • Walk every changed screen with TalkBack and VoiceOver. Flutter web: SemanticsBinding.instance.ensureSemantics() in main(). Debug with showSemanticsDebugger or flutter run -d chrome --profile --dart-define=FLUTTER_WEB_DEBUG_SHOW_SEMANTICS=true.

Samples

dart
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 ExcludeSemantics around interactive content, or use it instead of BlockSemantics to hide widgets behind a dialog.
  • MergeSemantics over two checkboxes or two tappable children.
  • Icon-only control with no tooltip, no Semantics.label, and no Icon.semanticLabel.
  • MediaQuery.of(context).textScaleFactor or Text(textScaleFactor:).
  • Disable text scaling at the app root, or shrink IconButton below 48.
  • Assume MediaQuery(data: copyWith(disableAnimations: true)) stops AnimationController.
  • Write the role into the label (label: 'Submit button').
  • Use Semantics.sortKey to fix keyboard Tab order.

Fix a screen

  1. Detect the existing a11y convention and reuse it.
  2. Leave stock Material/Cupertino widgets unlabeled unless they are custom-painted or icon-only.
  3. Give every icon-only control a tooltip or Semantics(label:, button: true). Pad the hit target to 48, not the glyph.
  4. Read MediaQuery.*Of. Honor largest text, high-contrast themes, screen-reader timeouts, and reduced motion.
  5. Add the guideline test. Walk TalkBack and VoiceOver.

Verify

  1. tester.ensureSemantics() + the four meetsGuideline checks + handle.dispose() on the screen you touched.
  2. Layout still fits at the OS largest text size on a small device.
  3. Contrast ≥ 4.5:1 (3:1 large text); state is not color-only.
  4. TalkBack and VoiceOver: every control has an intelligible name and the right role.
  5. Web: semantics enabled from main().

Powiązane zadania

z tego samego repozytorium

Więcej Skills

Wszystkie Skills