Files
galaxy-game/ui/docs/a11y.md
T
Ilia Denisov 642c5b7322
Tests · UI / test (push) Waiting to run
Tests · UI / test (pull_request) Successful in 2m9s
feat(ui): accessibility pass — WCAG 2.2 AA for login/lobby/shell (F2)
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>
2026-05-22 08:25:14 +02:00

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).