-
Notifications
You must be signed in to change notification settings - Fork 1k
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.
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.
- Name — every interactive element has an accessible name, and the widget itself is labeled (visible label,
aria-label, oraria-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
useEntryAnimationor is guarded byprefers-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).
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.