accessibility-as-code
Installation
SKILL.md
Accessibility is a coding standard, not a checklist
Accessibility is authored into the widget as you write it — the same tier as "the button actually works." There is no lint for any of this: flutter_lints and very_good_analysis ship zero a11y rules. Enforcement is the widget itself, review of your own diff, and widget tests. Applies to every interactive View, every Icon/Image, every state, and every color choice.
Testing mechanics (guideline matchers, golden lanes, RTL, honest conformance limits) live in widget-golden-and-a11y-testing; this skill is the authoring discipline.
Non-negotiable rules
- Every interactive node gets
Semantics(button: true, label: ...)— a rawGestureDetectorwith no semantics silently locks out every screen-reader and switch user. Correct semantics make TalkBack, VoiceOver, Switch Access and Switch Control work for free; it is definition-of-done, never a backlog ticket. - Every
Icon/Imagegets asemanticLabel, or is wrapped inExcludeSemanticsbecause it is decorative. There is no third option — an unlabelledIconis invisible to a screen reader. - Read a11y state from
MediaQueryat build time, never from app state.MediaQueryis anInheritedWidgetwith correct-by-construction invalidation; pushing it through a provider trades a compiler-guaranteed rebuild for a frame of staleness in the one area where being wrong is total failure. - Never
MediaQuery.withClampedTextScaling, nevertextScaleFactor. Both are clamping hacks that silently defeat the text-scale matrix while contrast and tap-target guidelines still pass green. Enforce with a source grep (see Anti-patterns). - Never
FittedBox, computedfontSize, orTextOverflow.ellipsisto make a label fit. They turn "doesn't fit at 200%" from a loud test failure into truncated or shrunk text on a device. Build flexible heights and let text wrap. - Never encode state through color alone. Pair every stateful signal with at least one non-color channel — icon + text label + shape/pattern + position. Color-only state is invisible under
invertColors, grayscale color-correction, and to every screen-reader user. - Contrast is measured against the composited background in both themes — body/icon text ≥ 4.5:1 (AA 1.4.3), large/hero text ≥ 3:1 (AA) — large text never has to clear a higher bar than body. Never test against the nominal token color when a fill, gradient, or translucency sits between text and surface.
- Every interactive target is ≥ 44×44 logical pixels and single-tap. No long-press or precise-gesture-only affordances; hand-off and motor-impaired use demand a large forgiving target.
- Traversal order is authored with
sortKey, never inherited from layout — visual position optimized for the thumb actively pessimises linear screen-reader/switch scanning. - Honor
boldText; keep haptics through reduced motion. HardcodingfontWeightthrows the setting away. ReaddisableAnimationsfromMediaQueryhere, but resolve how motion collapses throughdesign-system-structure's reduced-motion token +resolveMotionhelper (that skill owns the mechanism) — an animation that still conveys meaning with no reduced-motion path is a bug.