Skip to content

Accessibility Checklist

Cindy Zhang edited this page Aug 10, 2026 · 2 revisions

Accessibility Checklist

The accessibility bar for Astryx components. Every new component — and any change to an interactive one — must clear this checklist before review. Reviewers block on it (see the blocking criteria in .github/copilot-instructions.md), and it is a hard requirement for a lab → core promotion (see packages/lab/README.md).

This page is the canonical, editable home of the checklist so accessibility experts can refine it without a code PR. It is referenced by CONTRIBUTING.md and consumed by the Night Watch Component Auditor.

⚠️ Keep in sync. The checklist items map to shared primitives and CI enforcement in the repo. If you add or reword an item, check whether Component Audit Rubric §1 (which expands this checklist into graded checks) and the CONTRIBUTING pointer need a matching update.

Use the shared primitives

Compose the shared primitives — VisuallyHidden, useAnnounce, useFocusTrap, and the focus hooks (useListFocus, useGridFocus, useTreeFocus) — rather than hand-rolling equivalents. They implement the WAI-ARIA APG patterns and are tested once; a bespoke reimplementation of one is a review reject.

The checklist

  • Name — every interactive element has an accessible name, and the widget itself is labeled (visible label, aria-label, or aria-labelledby).
  • Role — the correct ARIA pattern from the WAI-ARIA APG; link the pattern you implemented in the PR description.
  • State — every state (selected, expanded, disabled, invalid, busy, …) is exposed via ARIA, never by color or shape alone.
  • Keyboard — full APG keyboard support for the pattern, including RTL arrow-key flipping via the shared focus hooks (they take isRtl).
  • Focus — overlays trap focus while open and restore it on close via useFocusTrap; focus is never dropped to <body> on a state change; the focus ring is visible in every theme.
  • Announcements — async state changes (loading, results, errors) announce via useAnnounce. Never render a live region that is born with content — assistive tech only announces changes to a live region, so it will be silent.
  • Reduced motion — entry/exit animation goes through useEntryAnimation or is guarded by prefers-reduced-motion.
  • Forced colors — painted state (selection fills, custom checkmarks, focus indicators) has system-color fallbacks so it survives forced-colors: active.
  • i18n — every AT-facing string (labels, announcements, hints) goes through useTranslator() / the i18n key system, same as visible text.
  • Target size — interactive targets are at least 24×24 px (WCAG 2.5.8).

What CI enforces (and what it can't)

CI is the enforcement layer, not a replacement for the checklist: the pr-a11y workflow runs an axe audit on every PR, a weekly workflow scans the full component surface, and the useAnnounce lint rule rejects hand-wired aria-live regions. axe only catches static, DOM-level issues — it cannot see keyboard behavior, focus management, or announcement timing. Those are exactly what this checklist and the component's unit tests cover.

Clone this wiki locally