Accessibility — requirements
Epic — release gate (floor) + differentiator (panel). MVP-1 requirements for the a11y floor baked into every DS atom (see the feature architecture). These are pass/fail launch conditions, audited each release. The per-member preferences panel (reduced-motion toggle, text scale, high-contrast, dyslexia font, focus mode) is MVP-2 — out of scope here beyond the floor. Accessibility is a ⭐ differentiator: Rewhaven's audience is neurodivergent and predictable, low-overwhelm UI is therapeutic, not cosmetic.
| Type | Non-functional ⭐ |
| Layer (build approach) | Frontend — DS prefs + a11y floor |
| RICE | R 7 × I 2 × C 80% / E 3 = 3.7 · Tier MVP-2 (floor = MVP-1) |
| KPI (summary) | WCAG AA pass %; a11y-pref adoption — see Success criteria |
| Traces to | feature accessibility · C4 design-system · prioritization · visual-timers |
| Depends on | design_system (one token source DsTheme; a11y floor in every atom) |
Success criteria (definitive KPI)
Success = 100% of DS atoms pass the automated a11y floor (contrast AA, minimum hit-target, semantics) AND zero P1 a11y defects — every release; manual screen-reader pass on core flows green.
These gates hold at 100%, verified at every release:
- Contrast: every color pair in
DsTheme— dark (default) and light — meets WCAG 2.2 AA: 4.5 : 1 normal text; 3 : 1 large text and UI components. - Hit-targets: every tappable DS atom provides a 48 × 48 dp minimum area; no exceptions for icon-only controls.
- Semantics: every DS atom carries
Semanticslabel, role, and state; no unlabelled interactive widget reaches a release build. - Reduced-motion:
MediaQuery.disableAnimationsis honored app-wide; no motion token bypasses the flag. - Focus order: switch-access and keyboard traversal is logical on every screen; no focus trap without an explicit escape.
- Screen reader (SR): a manual VoiceOver + TalkBack pass on four core flows (onboarding, today-chores, token wallet, settings) is green before every release.
- Scalable text: no content truncates or overflows at 200% system font scale.
Window: every release is a gate — no ramp. A single P1 defect (contrast failure, missing semantic label, broken SR flow) fails the release.
Measurement & audit
| Check | Trigger | Mechanism |
|---|---|---|
| Contrast AA (both themes) | CI — every PR | Token-contract tests assert color-pair ratios |
| Hit-target size | CI — every PR | Atom widget tests assert size >= 48×48 dp |
| Semantics / labels | CI — every PR | SemanticsController assertions on every atom story |
| Reduced-motion gate | CI — every PR | Widget test with disableAnimations: true; assert no frame delay |
| Screen-reader pass | Pre-release manual | VoiceOver (iOS) + TalkBack (Android) on 4 core flows |
| Scalable-text overflow | Pre-release visual regression | Widgetbook screenshots at textScaleFactor 1.0 / 1.5 / 2.0 |
| A11y CI gate | CI dashboard | Fail = block merge; zero tolerance on contrast / semantics / hit-target |
Telemetry privacy model: any future a11y aggregate telemetry = event-type + coarse count only; no child identity, no session content, never marketing; internal-BI only. No a11y preference data is collected until MVP-2 preferences exist.
Scope
The a11y floor applies to every atom, molecule, and template in packages/design_system/
and every app screen that composes them, across both dark (default) and light themes.
This epic concerns infrastructure-level accessibility: correct tokens, semantics,
sizing, motion behavior, and a green SR pass on core flows.
Per-member preferences — the levers that let each household member tune the a11y experience to their neurodivergent profile — are MVP-2. The floor established here makes those preferences non-breaking additions rather than reworks.
Non-functional requirements
NFR-A11Y-1 — Semantic labels and roles on every DS atom
Priority: P1 · Status: 🔨 to build
Statement. Every interactive and informational DS atom carries Semantics
annotations — label, role, and state — so screen readers announce correctly without
app-layer patches.
Acceptance — Given any DS atom in a widget test When SemanticsController
is queried Then every tappable has a non-empty label, a correct role (button,
checkbox, image, etc.), and state flags match the visual state.
NFR-A11Y-2 — Minimum hit-target size (48 × 48 dp)
Priority: P1 · Status: 🔨 to build
Statement. Every tappable DS atom — including icon-only controls, chip closers,
and navigation items — presents at least a 48 × 48 dp tappable area via padding or
touch-slop, supporting motor-impaired and neurodivergent users.
Acceptance — Given any tappable DS atom When measured in a widget test
Then tappableSize.width >= 48 && tappableSize.height >= 48; CI fails the PR
on any regression.
NFR-A11Y-3 — WCAG AA contrast in both themes
Priority: P1 · Status: 🔨 to build
Statement. All text and interactive-state color pairs in DsTheme meet WCAG 2.2
AA in dark (default) and light themes; no DS atom hard-codes a color outside
DsTheme.
Acceptance — Given the token-contract test suite runs in CI When all
ColorToken pairs are evaluated Then every pair passes the AA ratio; any
hardcoded color causes a test failure.
NFR-A11Y-4 — Reduced-motion honored app-wide
Priority: P1 · Status: 🔨 to build
Statement. The app forwards MediaQuery.disableAnimations to DsTheme's
motion_tokens; every DS atom that animates respects the flag — no motion plays
when it is set.
Acceptance
- Given
disableAnimations = trueWhen any motion-bearing DS atom renders Then the animation completes instantly (≤ 1 frame) or crossfades in ≤ 100 ms. - Given the CI widget test suite with
disableAnimations: trueWhen run Then noAnimationControllerproduces a visible frame delay.
NFR-A11Y-5 — Logical focus order; no unintended focus traps
Priority: P1 · Status: 🔨 to build
Statement. Tab and switch-access traversal follows reading-logical order on every
screen; no modal, sheet, or overlay traps focus without an announced escape route.
Acceptance — Given switch-access or keyboard navigation When traversing
any screen Then focus visits every interactive element in top-to-bottom,
left-to-right order (or explicit FocusOrder); no dead ends.
NFR-A11Y-6 — Screen-reader pass on core flows
Priority: P1 · Status: 🔨 to build Statement. A structured manual SR pass (VoiceOver iOS + TalkBack Android) on onboarding, today-chores, token wallet, and settings is green before every release: no unlabelled controls, broken reading order, or silent interactive elements. Acceptance — Given the pre-release SR checklist is executed on a physical device When all four flows are traversed Then zero P1 findings (unlabelled tappable, wrong role, broken order, inaccessible modal).
NFR-A11Y-7 — Scalable text — no truncation or overflow at 200% scale
Priority: P1 · Status: 🔨 to build
Statement. Every DS atom that displays text renders correctly at textScaleFactor
1.0, 1.5, and 2.0 — no truncation without a full-text Tooltip or semantic label,
no pixel overflow, no layout collapse.
Acceptance — Given Widgetbook visual-regression screenshots at 1.0 / 1.5 / 2.0
When compared to baseline Then zero new overflow indicators and no unintended
maxLines truncation without a full-text alternative.
Architecture considerations
- One token source (
DsTheme) is the enforcement point. Contrast ratios, sizing minimums, and motion-duration tokens are defined once; fixing a token fixes every atom. No DS atom may hard-code a color, size, or duration outsideDsTheme. - Reduced-motion is a first-class
motion_tokensconcern, not an ad-hoc widget check.DsThemeexposes areducedMotionflag derived fromMediaQuery.disableAnimations; every animating atom branches on it. - Semantics live in the atom, not the screen. App screens may augment (add a
dynamic value to a static label) but must never patch missing labels; the DS atom
owns its
Semanticswrapper. - Flutter a11y test utilities (
SemanticsController,tester.getSemantics) enable CI-level semantic contract tests without a real device or SR daemon. - Visual regression at
textScaleFactor— Widgetbook golden screenshots at three scale factors catch overflow regressions before human QA. - No a11y PII: any future adoption telemetry is a coarse aggregate (event-type + count); no child identity — consistent with the internal-BI model in Analytics.
Design work (ahead of build)
- A11y token decisions — explicit AA contrast ratios per text size, minimum
tap-target values in the spacing token scale, and focus-ring color tokens for both
themes — must be set in
DsThemebefore atom build begins. - Reduced-motion variants — for each motion-heavy atom (token-award celebration, timer countdown, progress indicator), a reduced-motion design must be spec'd: instant cut or 100 ms crossfade, and what the final static state looks like.
- Semantics conventions — a DS-level convention document: how labels are worded
per atom type, which atoms use
MergeSemantics, how state (checked,expanded,selected) is announced; written before atoms are built. - Pre-release SR checklist — the structured VoiceOver + TalkBack acceptance script for the four core flows, authored alongside the atom specs so QA has deterministic pass/fail criteria.
Decisions (resolved for MVP-1)
Resolved — see the MVP-1 decisions log for the canonical record, rationale, and status legend (✅ decided · ⚖️ counsel confirms · 🔜 MVP-1.x).
- D-A11Y-1 — Contrast target. ✅ AAA body-text contrast (7:1) in the dark theme; AA (4.5:1) floor everywhere else and in the light theme (anchor). Dark-theme body tokens set to AAA.
- D-A11Y-2 — Reduced-motion style. ✅ Crossfade ≤ 100 ms when
disableAnimationsis true (not instant) — a soft fade reads as intentional. Encode asmotion_tokens.reducedMotionDuration = 100ms. - D-A11Y-3 —
textScaleFactorceiling. ✅ Formally support up to 200% (Android 14 cap) with no layout breakage; best-effort to 320% (iOS max) without an overflow guarantee. Layout constraints target 200%. - D-A11Y-4 — Focus-ring token. ✅ A dedicated
--focus-ringtoken = a high-contrast accent (calm-dusk accent at full chroma), distinct per theme (lighter ring on dark, darker on light), ≥ 3:1 against adjacent colours.
Out of scope (MVP-1)
- Per-member a11y preferences panel (reduced-motion toggle, text scale slider, high-contrast mode, dyslexia font, focus mode) → MVP-2; the floor here makes those preferences non-breaking additions.
- AAA contrast as a release gate → aspirational target pending the open decision above; AA is the MVP-1 gate.
- Dyslexia font switching (OpenDyslexic or licensed alternative) → MVP-2; Atkinson Hyperlegible body font provides a baseline floor.
- Automated E2E screen-reader testing (Appium + Accessibility Service) → MVP-2; the manual SR pass is the MVP-1 gate.
- Localized a11y labels → MVP-2; see Localization & i18n.
- A11y adoption telemetry → MVP-2 when preferences exist; no preference data is collected in MVP-1 beyond the standard internal-BI gate.