Text scaling
All screens must remain functional and readable at the system’s largest text size setting (iOS: Accessibility sizes up to 310% scale; Android: font scale up to 2.0x).- No text must be clipped or truncated without an accessible alternative.
- Layouts must reflow rather than overflow. Fixed-height containers that clip text are not acceptable.
- Interactive elements must not overlap when text is enlarged.
- Test at: iOS Accessibility Inspector with largest Dynamic Type size; Android with font size at maximum.
Screen readers
Required semantics
Every interactive element must have a semantic label. Flutter’sSemantics widget or Tooltip is used where the visual label is insufficient.
| Element | Requirement |
|---|---|
| Icon-only buttons | semanticLabel required |
| Check-in sliders | Label must include axis name and current value: “Body: 3 out of 5” |
| Mana bar | Label must convey remaining vs total: “18 of 34 mana remaining” |
| Task cards | Label must include task name and cost: “Shower, 4 mana” |
| Empty states | Must be readable: “No tasks today” |
| Loading states | Must announce: “Loading” |
| Error states | Must announce the message and the available action |
Navigation order
Focus order must follow visual reading order (top to bottom, left to right). Custom traversal order (FocusTraversalGroup) is required for any non-standard layout.
Screen-by-screen semantics
New screens added to the app must include a semantics review before release. The review checks:- All interactive elements have labels
- Focus order is logical
- State changes are announced (e.g. task marked done, pool updated)
- No redundant labels (e.g. “Button” without context)
Reduced motion
Users who enable “Reduce Motion” on their device must not see vestibular-triggering animations.Rules
- Entrances/exits: Replace sliding or scaling transitions with a simple cross-fade or no animation.
- Looping animations: Must be disabled entirely under reduced motion. No pulsing, spinning, or continuous movement.
- Pool and cost updates: Numeric changes must not animate between values under reduced motion.
- Duration cap (non-reduced): No transition in normal mode exceeds 300ms. Animations that feel slow are worse than no animation.
Implementation
Check the platform preference viaMediaQuery.of(context).disableAnimations. Apply to all animated widgets at the design system level (lib/ui/core/design/), not per-screen.
Contrast
Minimum contrast ratios follow WCAG 2.1 AA:| Element | Minimum ratio |
|---|---|
| Normal text (< 18pt) | 4.5:1 |
| Large text (>= 18pt bold or 24pt regular) | 3:1 |
| UI components and graphics | 3:1 |
#4249C9 must be verified against all background colours it appears on. Both light and dark themes must pass independently.
Contrast must be verified with a tool (e.g. Colour Contrast Analyser) before new colour pairings are introduced to the design system.
Touch targets
Minimum touch target size: 44x44pt (logical pixels) on all platforms.- Interactive elements smaller than 44x44pt in their visual size must have their tap area extended using
GestureDetectorwithHitTestBehavioror wrapping in a minimum-sizedInkWell. - Targets must have at least 8pt of non-interactive space between them to prevent mis-taps.
Acceptance criteria for new screens
Before a new screen ships, it must pass:- Renders correctly at maximum system text size (no clipping, no overflow)
- All interactive elements have semantic labels
- Focus order is logical under screen reader navigation
- State changes are announced
- Reduced motion preference disables or replaces all animations
- All text and UI elements meet WCAG AA contrast
- All touch targets are >= 44x44pt