642c5b7322
Add the a11y foundation and bring login, lobby, and the in-game shell to WCAG 2.2 AA: - Primitives: .sr-only + .skip-link (base.css), trapFocus (modal focus trap + restore) and restoreFocus (menu focus restore) actions, the --color-focus visible ring. - In-game shell: skip link + focusable main landmark; WAI-ARIA sidebar tabs (roving tabindex, arrow/Home/End, tabpanel wiring); menu Escape + focus restore (account / view / turn-navigator / map-toggles / bottom-tabs); mail compose as a role=dialog modal with a focus trap. - login / lobby / lobby-create: skip link + main landmark, field labels, role=alert / role=status live regions. - Map canvas: aria-label naming it a visual overview, with its data reachable by keyboard via the sidebar inspector and tables (accessible alternative; in-canvas keyboard nav deferred). Gates (chromium-desktop): tests/e2e/a11y-axe.spec.ts scans every top-level view for WCAG 2.2 AA violations (zero); a11y-keyboard.spec.ts covers the skip link, menu Escape+restore, and tab roving. Adds @axe-core/playwright. Docs: ui/docs/a11y.md (+ index). Marks F1 and F2 done in ui/PLAN-finalize.md. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
78 lines
3.9 KiB
Markdown
78 lines
3.9 KiB
Markdown
# Accessibility
|
|
|
|
The client targets **WCAG 2.2 AA**. This doc records how accessibility is
|
|
built in, what is verified automatically, and the deliberate boundaries.
|
|
|
|
## Tooling & gates
|
|
|
|
- **axe-core via Playwright** (`@axe-core/playwright`):
|
|
[`tests/e2e/a11y-axe.spec.ts`](../frontend/tests/e2e/a11y-axe.spec.ts)
|
|
scans every top-level view — login, lobby, lobby/create, and the
|
|
in-game map / report / mail / battle / science-designer / table — with
|
|
the `wcag2a wcag2aa wcag21a wcag21aa wcag22aa` rule tags and asserts
|
|
zero violations. It runs once, on the `chromium-desktop` project (axe's
|
|
contrast and computed-role checks need one real engine; the
|
|
webkit/mobile projects add cost without new signal).
|
|
- **Keyboard navigation** (Playwright):
|
|
[`tests/e2e/a11y-keyboard.spec.ts`](../frontend/tests/e2e/a11y-keyboard.spec.ts)
|
|
covers the skip link, Escape-closes-and-restores-focus on menus, and
|
|
the sidebar tab arrow-key pattern.
|
|
- **Svelte compiler a11y lint** is on (no suppressions); `pnpm check`
|
|
fails on a11y warnings, so basic role/label/structure issues are caught
|
|
at build time.
|
|
|
|
## Shared primitives
|
|
|
|
- `.sr-only` (in [`base.css`](../frontend/src/lib/theme/base.css)) —
|
|
visually hidden, available to assistive tech.
|
|
- `.skip-link` — visible only on focus; each layout renders one as its
|
|
first focusable element, pointing at a `tabindex="-1"` main region.
|
|
- [`trapFocus`](../frontend/src/lib/a11y/focus-trap.ts) — Svelte action
|
|
for modal dialogs: moves focus in (to `[data-autofocus]`), cycles
|
|
Tab/Shift+Tab within, restores focus on close.
|
|
- [`restoreFocus`](../frontend/src/lib/a11y/restore-focus.ts) — Svelte
|
|
action for non-modal popovers/menus: returns focus to the trigger when
|
|
the surface closes, without trapping.
|
|
- `--color-focus` token drives a single visible focus ring app-wide
|
|
(`:where(*):focus-visible` in `base.css`).
|
|
|
|
## Coverage by area
|
|
|
|
- **Landmarks.** The in-game shell is `header` (banner) + `main` +
|
|
`aside` (complementary); login/lobby/create each wrap content in a
|
|
`main#main-content`. Every layout has a skip link to its main.
|
|
- **Forms** (login, lobby, create). Every control has an associated
|
|
label; submit/validation errors use `role="alert"`, async status uses
|
|
`role="status"`, so they are announced.
|
|
- **Sidebar tabs** follow the WAI-ARIA tabs pattern: `role="tablist"` /
|
|
`tab` / `tabpanel`, roving `tabindex`, arrow + Home/End keys with
|
|
automatic activation, `aria-controls` / `aria-labelledby` linking tabs
|
|
to the panel.
|
|
- **Menus & popovers** (account, view, turn navigator, map toggles,
|
|
mobile "more"): `aria-haspopup` / `aria-expanded` triggers, Escape and
|
|
outside-click dismissal, and `restoreFocus` so focus is never dropped
|
|
to `<body>`.
|
|
- **Modal dialog** (mail compose): `role="dialog"` + `aria-modal` +
|
|
`aria-labelledby`, `trapFocus`, Escape to close.
|
|
|
|
## The map canvas
|
|
|
|
The PixiJS/WebGL map is a visual surface that cannot be made keyboard- or
|
|
screen-reader-navigable without a bespoke effort. It is therefore handled
|
|
as an **accessible alternative**: the `<canvas>` carries an `aria-label`
|
|
naming it and pointing to where the same information lives, and every
|
|
datum it shows — planets, ship groups, routes — is reachable by keyboard
|
|
through the sidebar inspector and the tables view. In-canvas keyboard
|
|
navigation (cursoring between planets) is intentionally out of scope; it
|
|
is noted as a future enhancement in [`ROADMAP.md`](../ROADMAP.md).
|
|
|
|
## Boundaries / future work
|
|
|
|
- The deep AA pass focuses on login, lobby, and the in-game shell
|
|
(acceptance surfaces). Secondary views pass the axe structural scan;
|
|
exhaustive keyboard/SR polish of every view interaction is incremental.
|
|
- The battle scene (`battle-player/battle-scene.svelte`) is a data-viz
|
|
surface (see [`design-system.md`](design-system.md)); its content is
|
|
summarised by the adjacent text protocol log.
|
|
- In-canvas keyboard navigation for the map (deferred, above).
|