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

3.9 KiB

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 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 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) — 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 — Svelte action for modal dialogs: moves focus in (to [data-autofocus]), cycles Tab/Shift+Tab within, restores focus on close.
  • restoreFocus — 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.

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); its content is summarised by the adjacent text protocol log.
  • In-canvas keyboard navigation for the map (deferred, above).