Skip to main content

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’s Semantics widget or Tooltip is used where the visual label is insufficient.
ElementRequirement
Icon-only buttonssemanticLabel required
Check-in slidersLabel must include axis name and current value: “Body: 3 out of 5”
Mana barLabel must convey remaining vs total: “18 of 34 mana remaining”
Task cardsLabel must include task name and cost: “Shower, 4 mana”
Empty statesMust be readable: “No tasks today”
Loading statesMust announce: “Loading”
Error statesMust announce the message and the available action
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 via MediaQuery.of(context).disableAnimations. Apply to all animated widgets at the design system level (lib/ui/core/design/), not per-screen.
final reduceMotion = MediaQuery.of(context).disableAnimations;

AnimatedOpacity(
  duration: reduceMotion ? Duration.zero : const Duration(milliseconds: 200),
  opacity: visible ? 1.0 : 0.0,
  child: child,
)

Contrast

Minimum contrast ratios follow WCAG 2.1 AA:
ElementMinimum ratio
Normal text (< 18pt)4.5:1
Large text (>= 18pt bold or 24pt regular)3:1
UI components and graphics3:1
The primary brand colour #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 GestureDetector with HitTestBehavior or wrapping in a minimum-sized InkWell.
  • 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