| name | design-accessibility |
| description | Make a full-stack Rails 8.1 Hotwire UI accessible to WCAG 2.2 AA — semantic HTML, ARIA patterns, keyboard operability, visible focus, color contrast, screen-reader support, and the focus-management gotchas specific to Turbo Drive/Frames/Streams swaps — then prove it with axe automation in system tests. Menu-driven for the conformance target and the testing approach (default WCAG 2.2 AA, axe-core in Capybara system tests). This is the canonical accessibility rubric the other design-* skills cross-link to instead of repeating; apply when auditing or fixing a11y, adding ARIA, wiring keyboard/focus behavior, or setting up automated a11y tests. Web apps only. |
| metadata | {"owner":"rails-design","status":"stable"} |
| user-invocable | true |
| argument-hint | [component-or-page] |
design-accessibility
Purpose
The canonical accessibility rubric for the suite. Owns WCAG 2.2 AA conformance for
a Hotwire UI: semantic HTML first, ARIA only where HTML falls short, full keyboard
operability, always-visible focus, sufficient color contrast, screen-reader support,
and the focus-management problems unique to Turbo (Drive visits, Frame/Stream DOM
swaps that move focus or announce nothing). It then makes a11y enforceable with
axe-core in system tests. Every other design-* skill defers here for a11y rather
than re-documenting it — link to this skill, don't copy it.
When to Apply
Use this skill when the task is:
- Auditing a page/component against WCAG 2.2 AA or fixing reported a11y issues
- Adding ARIA (roles/states/properties) to a custom widget
- Making something keyboard operable (tab order, Esc/Enter/arrow keys, focus traps)
- Fixing focus after a Turbo Frame/Stream swap, modal open/close, or Drive visit
- Ensuring screen-reader announcements (live regions, labels, names)
- Wiring automated a11y tests (axe-core in Capybara, Lighthouse CI)
Do not use this skill when the task is:
- Choosing contrast-safe tokens at the source → read
../design-foundations/SKILL.md (it sets safe color pairs; this skill audits the result)
- The interaction mechanics of a modal/dropdown/toast → read
../design-interactions/SKILL.md (it builds them; link here for the a11y requirements)
- Form field/label/error structure → read
../design-forms/SKILL.md (it owns form UX; this skill owns the a11y rules it must meet)
- The Turbo/Stimulus plumbing itself → read
../rails-hotwire/SKILL.md
- The test stack setup (drivers, CI) → read
../rails-testing/SKILL.md (this skill adds a11y assertions on top)
- An API-only app — no UI to make accessible
Detect Before You Generate
grep -nE "api_only" config/application.rb
grep -E "axe-core-rspec|axe-core-capybara|axe-matchers|lighthouse" Gemfile.lock 2>/dev/null
ls spec/system test/system 2>/dev/null
grep -rlnE "aria-|role=" app/views app/components 2>/dev/null | head
grep -rnE "tabindex=\"[1-9]" app/views app/components 2>/dev/null
grep -rlnE "lang=" app/views/layouts 2>/dev/null
- If
axe-core-capybara/axe-core-rspec is installed, wire assertions into the
existing system-test setup (driver/framework owned by ../rails-testing/).
- Existing ARIA/
tabindex usage tells you where custom widgets already exist — audit
those first; they're the likeliest violations.
<html lang> and a real <title> per page are baseline — confirm they exist.
Menu
Two menus via AskUserQuestion; defaults marked Recommended.
Conformance target
| Option | One-line trade-off | Deep dive |
|---|
| WCAG 2.2 AA (Recommended) | The standard target for most apps and the bar most regulations reference. Comprehensive without AAA's extremes. | wcag-checklist.md |
| WCAG 2.2 AAA | Highest bar (7:1 contrast, etc.); rarely required wholesale, hard to sustain. Adopt specific criteria, not all. | wcag-checklist.md |
| WCAG 2.2 A | Minimum legal floor; leaves real barriers (contrast, focus). Use only as a stepping stone to AA. | wcag-checklist.md |
Automated a11y testing
| Option | One-line trade-off | Deep dive |
|---|
| axe-core in system tests (Recommended) | Runs the axe engine on real rendered pages in Capybara; catches ~30–50% of issues automatically, in CI. | axe-system-tests.md |
| Manual axe DevTools | Browser-extension scans during development; great for spot checks, not enforced in CI. | axe-system-tests.md |
| Lighthouse CI | Page-level a11y score in CI alongside perf; coarser than axe-core assertions. | axe-system-tests.md |
Decision Flow
- Target AA. It's the right default and what audits/regulations expect. Pull in a
specific AAA criterion (e.g. enhanced contrast) where it matters; don't chase AAA
wholesale. Treat A as a floor you're already past.
- Automate with axe-core in system tests and keep manual testing — automation
catches contrast, missing names/labels, and structural issues but cannot judge
meaningful focus order, sensible announcements, or whether a name describes its
control. Budget manual keyboard + screen-reader passes for key flows.
- Semantic HTML before ARIA. A
<button>, <a>, <nav>, <dialog>, <label>
carries role + keyboard behavior for free. Reach for ARIA only to fill gaps — and
remember "no ARIA is better than bad ARIA."
- Turbo changes the focus contract. Frame/Stream swaps and Drive visits move or
drop focus and don't announce changes by default. Manage focus and use live regions
explicitly — see focus-management-hotwire.md.
Problem → Reference
| Task | Read |
|---|
| Audit against WCAG 2.2 AA (the checklist, what's new in 2.2) | references/wcag-checklist.md |
| ARIA roles/states for custom widgets (and when NOT to use ARIA) | references/aria-patterns.md |
| Focus after Turbo Frame/Stream swaps, modal open/close, Drive visits | references/focus-management-hotwire.md |
| Keyboard operability: tab order, Esc/Enter/arrows, skip links, traps | references/keyboard-navigation.md |
| Color contrast audit (AA ratios, non-color cues) | references/color-contrast.md |
| Screen-reader testing (VoiceOver/NVDA), live regions, names | references/screen-reader-testing.md |
| Automated a11y tests: axe-core in Capybara, CI wiring | references/axe-system-tests.md |
Verify
Accessibility is verified by machine + keyboard + screen reader — all three:
bin/rails test test/system 2>/dev/null || bundle exec rspec spec/system
A page is accessible when axe is clean and it's fully keyboard-operable and a
screen reader narrates it sensibly. Wire the axe assertion into ../rails-testing/'s
suite so regressions fail CI.