# Accessibility pass A pass over every route the PWA serves (the routes in [STATES.md](STATES.md)) for launch, on 2026-10-10. Each route was checked for: - **Keyboard** — every action reachable and operable with the keyboard alone. - **Focus** — a visible focus ring on everything that takes focus. - **Names and roles** — what a screen reader announces: switches, checkboxes, the current nav item, dialogs and their labels. - **Contrast** — text at least 4.5:1 against its ground, for every token pair used for text. - **Hit targets** — every tap target at least 44 × 44 px. - **Reduced motion** — what `prefers-reduced-motion: reduce` turns off. The pass read each screen's markup and stylesheets, computed contrast from [tokens.css](../web/src/styles/tokens.css), and checked the fixes' computed styles in Chrome. Severity: **high** blocks a task for keyboard or screen-reader users; **medium** fails a criterion above but leaves a way through; **low** is a gap outside the criteria above, or a polish item. High and medium findings are fixed in this pass with a regression test; low ones are follow-ups. ## Shared building blocks Most of the criteria are met once, in the shared components every screen uses: | Concern | Where | Behaviour | | --- | --- | --- | | Focus ring | [base.css](../web/src/styles/base.css) | `:focus-visible` draws a 2 px grape (`--color-accent`) outline 2 px out, on every focusable element. | | Dialogs | [Sheet](../web/src/ui/Sheet.tsx) | `role="dialog"`, `aria-modal="true"`, and a required `aria-label` or `aria-labelledby` (the type will not compile without one). Opening moves focus in, Tab is trapped, Escape and the scrim close, the page behind is `inert`, and focus returns to the opener. Every sheet is labelled by its visible title. | | Toggles | [Toggle](../web/src/ui/Toggle.tsx) | A `