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). The app clampsMediaQuery.textScaler to the range 0.85x – 1.4x at the root (main.dart). This honors user preference up to the WCAG AA target while preventing layouts from breaking at extreme settings. Anything above 1.4x is rendered as 1.4x.
- 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.
- Golden tests in
app/test/design/primitives/exercise primitives at 1.0x and 1.3x scale to catch regressions.
Typography
The app ships a single canonical type scale defined inapp/lib/core/design/tokens/typography.dart and wired into ThemeData.textTheme. Feature code must reach for Theme.of(context).textTheme.* rather than hardcoding fontSize: values. The architecture test test/architecture/design_system_discipline_test.dart enforces this.
| Token | Size | Usage |
|---|---|---|
displayLarge | 44 | Hero stat numbers (AppStat.hero) |
displayMedium | 36 | Primary screen heroes |
displaySmall | 32 | Smaller heroes |
headlineMedium | 28 | Screen titles |
headlineSmall | 22 | Section titles |
titleLarge | 20 | Card titles |
titleMedium | 18 | Subsection titles |
titleSmall | 16 | Small titles |
bodyLarge | 18 | Primary body text |
bodyMedium | 17 | Default body |
bodySmall | 13 | Caption / hint |
labelLarge | 15 | Button text |
labelMedium | 13 | Chip / tag |
labelSmall | 11 | Chart axis (auto-scales) |
withTabularFigures(...)) so digits stay aligned as values change.
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 through the generated Material 3 light and dark colour schemes. Both schemes must pass independently anywhere their derived roles are used for text, UI components, or graphics.
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