git:20260202.b236384 to git:20260316.00cb75b

72 added, 154 removed. Audit A to A.

---
name: web-accessibility-web-accessibility
description: WCAG, ARIA, keyboard navigation
---
# Accessibility
- > **Quick Guide:** All interactive elements keyboard accessible. Use Radix UI for ARIA patterns. WCAG AA minimum (4.5:1 text contrast). Proper form labels and error handling. Test with axe DevTools and screen readers.
+ > **Quick Guide:** All interactive elements keyboard accessible. Use headless component libraries for ARIA patterns. WCAG AA minimum (4.5:1 text contrast). Proper form labels and error handling. Combine automated axe-core checks with manual keyboard and screen reader testing.
---
<critical_requirements>
## CRITICAL: Before Using This Skill
> **All code must follow project conventions in CLAUDE.md** (kebab-case, named exports, import ordering, `import type`, named constants)
**(You MUST ensure all interactive elements are keyboard accessible with visible focus indicators)**
- **(You MUST use Radix UI components for built-in ARIA patterns instead of manual implementation)**
+ **(You MUST use headless component libraries for complex ARIA patterns instead of manual implementation)**
**(You MUST maintain WCAG AA minimum contrast ratios - 4.5:1 for text, 3:1 for UI components)**
**(You MUST never use color alone to convey information - always add icons, text, or patterns)**
</critical_requirements>
---
- **Auto-detection:** Accessibility (a11y), WCAG compliance, ARIA patterns, keyboard navigation, screen reader support, Radix UI, focus management
+ **Auto-detection:** Accessibility (a11y), WCAG compliance, ARIA patterns, keyboard navigation, screen reader support, focus management, aria-label, aria-live, role attribute, skip link
**When to use:**
- Implementing keyboard navigation and focus management
- - Using Radix UI for accessible component patterns (built-in a11y)
- Ensuring WCAG AA color contrast (4.5:1 text, 3:1 UI components)
- - Testing with axe DevTools and screen readers
- - Building interactive components (buttons, forms, modals, tables)
+ - Building interactive components (buttons, forms, modals, tables) with proper ARIA
- Adding dynamic content updates (live regions, status messages)
+ - Implementing skip links, landmarks, and semantic HTML structure
+ - Handling motion preferences with `prefers-reduced-motion`
**When NOT to use:**
- Working on backend/API code with no UI
- Writing build scripts or configuration files
- Creating documentation or non-rendered content
- Working with CLI tools (different accessibility considerations)
**Target:** WCAG 2.2 Level AA compliance (minimum), AAA where feasible
- **Key patterns covered:**
-
- - Keyboard navigation standards (tab order, focus management, skip links, Escape to close)
- - ARIA patterns with Radix UI components (prefer Radix for built-in accessibility)
- - WCAG 2.2 AA compliance minimum (contrast ratios, semantic HTML, touch targets 24x24px minimum)
- - Screen reader support (role-based queries, hidden content, live regions)
-
- **Detailed Resources:**
-
- - For code examples, see [examples/](examples/) directory:
- - [core.md](examples/core.md) - Skip links, semantic HTML, landmarks, button vs link
- - [forms.md](examples/forms.md) - Form validation, error handling, Radix select
- - [focus.md](examples/focus.md) - Modal dialogs, focus indicators
- - [color.md](examples/color.md) - Contrast, color-independent indicators, tokens
- - [tables.md](examples/tables.md) - Sortable data tables
- - [touch-targets.md](examples/touch-targets.md) - Touch target sizing
- - [screen-reader.md](examples/screen-reader.md) - sr-only, hiding decorative content
- - [testing.md](examples/testing.md) - Role queries, jest-axe, Lighthouse CI
- - For decision frameworks and anti-patterns, see [reference.md](reference.md)
-
---
<philosophy>
## Philosophy
- Accessibility ensures digital products are usable by everyone, including users with disabilities. This skill applies the principle that **accessibility is a requirement, not a feature** - it should be built in from the start, not retrofitted.
+ Accessibility ensures digital products are usable by everyone, including users with disabilities. **Accessibility is a requirement, not a feature** - it should be built in from the start, not retrofitted.
Key philosophy:
- **Semantic HTML first** - Use native elements for built-in accessibility
- - **Radix UI for complex patterns** - Leverage tested, accessible component primitives
+ - **Headless components for complex patterns** - Leverage tested, accessible component primitives from your component library
- **Progressive enhancement** - Start with keyboard, add mouse interactions on top
- **WCAG as baseline** - Meet AA minimum, aim for AAA where feasible
</philosophy>
---
<patterns>
## Core Patterns
### Keyboard Navigation Standards
**CRITICAL: All interactive elements must be keyboard accessible**
#### Tab Order
- **Logical flow** - Tab order must follow visual reading order (left-to-right, top-to-bottom)
- **No keyboard traps** - Users can always tab away from any element
- **Skip repetitive content** - Provide skip links to main content
- **tabindex rules:**
- `tabindex="0"` - Adds element to natural tab order (use sparingly)
- `tabindex="-1"` - Programmatic focus only (modal content, headings)
- Never use `tabindex > 0` (creates unpredictable tab order)
#### Focus Management
- **Visible focus indicators** - Always show clear focus state (never `outline: none` without replacement)
- **Focus on open** - When opening modals/dialogs, move focus to first interactive element or close button
- **Focus on close** - Restore focus to trigger element when closing modals/dialogs
- - **Focus trapping** - Trap focus inside modals using Radix UI or manual implementation
+ - **Focus trapping** - Trap focus inside modals using your headless component library or manual implementation
- **Programmatic focus** - Use `element.focus()` for dynamic content (search results, error messages)
#### Keyboard Shortcuts
- **Standard patterns:**
- `Escape` - Close modals, cancel actions, clear selections
- `Enter/Space` - Activate buttons and links
- `Arrow keys` - Navigate lists, tabs, menus, sliders
- `Home/End` - Jump to first/last item
- `Tab/Shift+Tab` - Navigate between interactive elements
#### Skip Links
- **MANDATORY for pages with navigation**
+ **MANDATORY for pages with navigation** - place as first focusable element, visually hidden until focused.
- - Place skip link as first focusable element
- - Visually hidden until focused
- - Allow users to skip navigation and jump to main content
- - Multiple skip links for complex layouts (skip to navigation, skip to sidebar, etc.)
+ ```typescript
+ // components/skip-link.tsx
+ export function SkipLink({ className }: { className?: string }) {
+ return (
+ <a href="#main-content" className={className}>
+ Skip to main content
+ </a>
+ );
+ }
+ ```
- ---
+ See [examples/core.md](examples/core.md) for full skip link implementation with styling.
- ### ARIA with Radix UI
+ ---
- **Prefer Radix UI components** - They handle ARIA automatically:
+ ### ARIA Patterns
- - `Dialog` - focus trapping, Escape to close, aria-modal
- - `Select` - keyboard navigation, aria-selected, listbox pattern
- - `Tabs` - arrow key navigation, aria-selected
- - `Tooltip` - proper timing, keyboard support
- - `Popover` - focus management, click outside to close
+ **Use headless component libraries** (Radix UI, Headless UI, React Aria, Ariakit) - they handle ARIA automatically for complex patterns like dialogs, selects, tabs, tooltips, and popovers.
#### Component-Specific ARIA
**Buttons:**
- `aria-label` - For icon-only buttons
- `aria-pressed` - For toggle buttons
- `aria-expanded` - For expandable sections
- `aria-disabled` - Use with `disabled` attribute
**Forms:**
- `aria-required` - Required fields (use with `required`)
- `aria-invalid` - Invalid fields
- `aria-describedby` - Link to error messages, helper text
- `aria-errormessage` - Explicit error message reference
**Navigation:**
- `aria-current="page"` - Current page in navigation
- `aria-label` - Describe navigation purpose ("Main navigation", "Footer navigation")
**Modals/Dialogs:**
- `role="dialog"` or `role="alertdialog"`
- `aria-modal="true"`
- `aria-labelledby` - Points to dialog title
- `aria-describedby` - Points to dialog description
**Tables:**
- `scope="col"` and `scope="row"` for headers
- `<caption>` for table description
- `aria-sort` for sortable columns
#### Live Regions
**Use for dynamic content updates:**
- `aria-live="polite"` - Announce when user is idle (status messages, non-critical updates)
- `aria-live="assertive"` - Announce immediately (errors, critical alerts)
- `aria-atomic="true"` - Announce entire region content
- `role="status"` - For status messages (implies `aria-live="polite"`)
- `role="alert"` - For error messages (implies `aria-live="assertive"`)
**Best practices:**
- Keep messages concise and meaningful
- Clear old messages before new ones
- Don't spam with rapid updates (debounce)
---
### Color Contrast & Visual Design
**Text contrast (AA):**
- Normal text (< 18px): 4.5:1 minimum
- Large text (>= 18px or >= 14px bold): 3:1 minimum
- AAA (recommended): 7:1 for normal, 4.5:1 for large
**Non-text contrast:**
- UI components (buttons, form inputs): 3:1 minimum
- Focus indicators: 3:1 against background
- Icons (functional): 3:1 minimum
**Color Independence:**
- Add icons to color-coded states (check for success, X for error)
- Use text labels with status colors
- Provide patterns/textures in charts
- Underline links in body text
+ See [examples/color.md](examples/color.md) for contrast examples and design tokens.
+
---
### Semantic HTML
**Always use semantic HTML:**
- `<button>` for actions (not `<div onclick>`)
- `<a>` for navigation (not `<div onclick>`)
- `<nav>` for navigation sections
- `<main>` for primary content (one per page)
- `<header>` and `<footer>` for page sections
- `<ul>/<ol>` for lists
- `<table>` for tabular data (not divs with grid CSS)
- `<form>` with proper `<label>` associations
**Never:**
- Use `<div>` or `<span>` for interactive elements
- Use click handlers on non-interactive elements without proper role
- Use tables for layout
- Use placeholder as label replacement
---
### Form Accessibility
**Label Associations:**
- - Always use proper `<label>` with `for` attribute
+ - Always use proper `<label>` with `htmlFor` attribute
- Or wrap input inside label element
**Error Handling:**
- `aria-invalid="true"` on invalid fields
- `aria-describedby` linking to error message
- `role="alert"` on error messages for screen reader announcement
- Visual error indicators (icons, border color)
- Error summary at top of form for multiple errors
- **Required Fields:**
-
- - `required` attribute for browser validation
- - `aria-required="true"` for screen readers
- - Visual indicator (asterisk, "required" text)
- - Legend/description explaining required fields
-
- **Input Types:**
-
- - `type="email"` - Email keyboard
- - `type="tel"` - Phone keyboard
- - `type="number"` - Number keyboard
- - `type="date"` - Date picker
- - `type="search"` - Search keyboard
+ See [examples/forms.md](examples/forms.md) for complete form validation and error handling patterns.
---
### Focus Indicators
**Minimum requirements:**
- 3:1 contrast ratio against background
- 2px minimum thickness
- Clear visual difference from unfocused state
- Consistent across all interactive elements
**Use `:focus-visible` for better UX:**
- - `:focus` - Shows on mouse click (annoying)
- - `:focus-visible` - Shows only for keyboard navigation (better)
+ ```css
+ /* Shows only for keyboard navigation, not mouse clicks */
+ button:focus-visible {
+ outline: 2px solid var(--color-primary);
+ outline-offset: 2px;
+ }
+ ```
+ See [examples/focus.md](examples/focus.md) for full focus indicator patterns.
+
---
### Touch Target Sizes
**WCAG 2.2 Target Size Requirements:**
- **Level AA (2.5.8 Target Size Minimum):**
-
- - Minimum: 24x24 CSS pixels
- - OR adequate spacing from adjacent targets (24px between closest points)
+ - **Level AA (2.5.8):** 24x24 CSS pixels minimum, or adequate spacing (24px between targets)
+ - **Level AAA (2.5.5):** 44x44 CSS pixels (recommended for best UX)
- Exceptions: inline text links, user agent controls, essential designs
- **Level AAA (2.5.5 Target Size Enhanced):**
-
- - Minimum: 44x44 CSS pixels (recommended for best UX)
-
- **Interactive elements:**
-
- - Buttons: 24x24px minimum (44x44px recommended)
- - Links in text: Inline exemption applies, but increase padding where feasible
- - Form inputs: 24px height minimum (44px recommended)
- - Icons: 24x24px minimum touch target
-
- **Spacing:**
-
- - 8px minimum between adjacent touch targets
- - More spacing on mobile (12-16px recommended)
- - Alternative: 24px spacing allows smaller targets
+ See [examples/touch-targets.md](examples/touch-targets.md) for sizing and spacing examples.
---
### Screen Reader Support
- **Visually Hidden Content:**
-
- Use `.sr-only` class for screen reader only text
- Use `aria-label` for icon-only buttons
- Use empty `alt=""` for decorative images
-
- **Hidden from Screen Readers:**
+ - Use `aria-hidden="true"` for decorative content (but never on focusable elements)
- - `aria-hidden="true"` for decorative content
- - Empty `alt=""` for decorative images
+ See [examples/screen-reader.md](examples/screen-reader.md) for sr-only and hiding patterns.
---
### Motion and Animation Accessibility
**WCAG 2.3.3 Animation from Interactions (AAA):**
- Motion can cause nausea, dizziness, or vestibular disorders for some users. Provide motion alternatives:
-
- **Use `prefers-reduced-motion` media query:**
+ Motion can cause nausea, dizziness, or vestibular disorders (affects 70+ million people). Use `prefers-reduced-motion` to respect user preferences:
```css
- /* Approach 1: Disable animations when user prefers reduced motion */
- @media (prefers-reduced-motion: reduce) {
- *,
- *::before,
- *::after {
- animation-duration: 0.01ms !important;
- animation-iteration-count: 1 !important;
- transition-duration: 0.01ms !important;
- }
- }
-
- /* Approach 2: Only animate when user has no preference */
+ /* Only animate when user has no motion preference */
@media (prefers-reduced-motion: no-preference) {
.animated-element {
animation: slide-in 300ms ease-out;
}
}
```
**Key principles:**
- `reduce` means minimize, not eliminate all motion - essential animations can remain
- Replace motion effects (scale, rotate, slide) with non-motion effects (fade, dissolve, color change)
- Provide pause/stop controls for auto-playing content longer than 5 seconds
- Avoid parallax scrolling effects or provide alternatives
---
### WCAG 2.2 New Success Criteria
- **WCAG 2.2** (October 2023) added 9 new success criteria. Key ones for developers:
+ **WCAG 2.2** (W3C Recommendation October 2023, ISO/IEC 40500:2025 as of October 2025) added 9 new success criteria. Key ones for developers:
**Level A:**
- **3.2.6 Consistent Help** - Help mechanisms (contact, chat) must appear in same relative order across pages
- **3.3.7 Redundant Entry** - Previously entered info must be auto-populated or available for selection
**Level AA:**
- **2.4.11 Focus Not Obscured (Minimum)** - Focused element must not be entirely hidden by other content (sticky headers, modals)
- **2.5.7 Dragging Movements** - Provide single-pointer alternative to drag operations
- **2.5.8 Target Size (Minimum)** - 24x24px minimum or adequate spacing
- **3.3.8 Accessible Authentication** - No cognitive function tests (CAPTCHAs) unless alternatives exist
**Level AAA:**
- **2.4.12 Focus Not Obscured (Enhanced)** - No part of focus indicator hidden
- **2.4.13 Focus Appearance** - Focus indicator 2px perimeter, 3:1 contrast
- **3.3.9 Accessible Authentication (Enhanced)** - Stricter CAPTCHA requirements
**Removed from WCAG 2.2:**
- **4.1.1 Parsing** - Obsolete due to modern browser error correction
</patterns>
---
- <testing>
-
- ## Testing Approach
-
- **RECOMMENDED: Multi-layered testing strategy**
-
- ### Automated Testing
-
- **Use Testing Library's role-based queries:**
-
- ```typescript
- const button = screen.getByRole("button", { name: "Submit" });
- const switchElement = within(feature).getByRole("switch");
- ```
-
- **Additional tools:**
-
- - **jest-axe** - Automated accessibility testing in Jest unit tests
- - **vitest-axe** - Same API as jest-axe, but for Vitest projects
- - **cypress-axe** - E2E accessibility testing with Cypress
- - **axe-core** - Runtime accessibility testing (v4.10+ supports WCAG 2.2)
- - **eslint-plugin-jsx-a11y** - Lint-time accessibility checks
+ ## Testing Accessibility
- **Note:** axe-core finds ~57% of WCAG issues automatically. Always combine with manual testing.
+ **Multi-layered approach required** - automated tools catch ~57% of WCAG issues. Always combine with manual testing.
- ### Manual Testing Checklist
+ **Automated:** Use axe-core based tools in your test runner for automated WCAG checks. See [examples/testing.md](examples/testing.md) for axe integration patterns.
- **Keyboard navigation:**
+ **Manual keyboard checklist:**
- [ ] Tab through all interactive elements in logical order
- [ ] Activate buttons with Enter/Space
- [ ] Close modals with Escape
- [ ] Navigate dropdowns with arrows
- [ ] No keyboard traps
- [ ] Focus indicators visible on all elements
- **Screen reader:**
+ **Manual screen reader checklist:**
- [ ] All images have alt text (or alt="" if decorative)
- [ ] Form inputs have labels
- [ ] Error messages are announced
- - [ ] Button purposes are clear
- [ ] Headings create logical outline
- [ ] Landmarks are labeled
- [ ] Live regions announce updates
- - [ ] Tables have proper headers
- **Visual:**
+ **Manual visual checklist:**
- [ ] Color contrast meets WCAG AA (4.5:1 text, 3:1 UI)
- [ ] Information not conveyed by color alone
- [ ] Text resizable to 200% without horizontal scroll
- - [ ] Touch targets meet 24x24px minimum (AA) or 44x44px (AAA recommended)
+ - [ ] Touch targets meet 24x24px minimum (AA)
- [ ] Focus indicators have 3:1 contrast
- [ ] Animations respect prefers-reduced-motion
- ### Screen Reader Testing
+ **Screen readers to test with:** NVDA (Windows, free), JAWS (Windows, paid), VoiceOver (macOS/iOS, built-in), TalkBack (Android, built-in)
- **Test with multiple screen readers:**
+ ---
- - **NVDA** (Windows) - Free, most popular
- - **JAWS** (Windows) - Industry standard
- - **VoiceOver** (macOS/iOS) - Built-in
- - **TalkBack** (Android) - Built-in
+ **Key patterns covered:**
- </testing>
+ - Keyboard navigation standards (tab order, focus management, skip links, Escape to close)
+ - ARIA patterns with headless component libraries (built-in accessibility)
+ - WCAG 2.2 AA compliance minimum (contrast ratios, semantic HTML, touch targets 24x24px minimum)
+ - Screen reader support (role-based queries, hidden content, live regions)
+ **Detailed Resources:**
+
+ - [examples/core.md](examples/core.md) - Skip links, semantic HTML, landmarks, button vs link
+ - [examples/forms.md](examples/forms.md) - Form validation, error handling, accessible select
+ - [examples/focus.md](examples/focus.md) - Modal dialogs, focus indicators
+ - [examples/color.md](examples/color.md) - Contrast, color-independent indicators, tokens
+ - [examples/tables.md](examples/tables.md) - Sortable data tables
+ - [examples/touch-targets.md](examples/touch-targets.md) - Touch target sizing
+ - [examples/screen-reader.md](examples/screen-reader.md) - sr-only, hiding decorative content
+ - [examples/testing.md](examples/testing.md) - Accessibility testing with axe-core
+ - [reference.md](reference.md) - Decision frameworks, anti-patterns, WCAG quick reference
+
---
## Resources
**Official guidelines:**
- WCAG 2.2 Guidelines: https://www.w3.org/WAI/WCAG22/quickref/
- What's New in WCAG 2.2: https://www.w3.org/WAI/standards-guidelines/wcag/new-in-22/
- WAI-ARIA Authoring Practices: https://www.w3.org/WAI/ARIA/apg/
- - WAI-ARIA 1.3 Draft (new roles: suggestion, comment, mark): https://w3c.github.io/aria/
- **Note:** WAI-ARIA 1.3 is still in draft (as of January 2024). New roles like `suggestion`, `comment`, and `mark` are not yet widely supported by browsers and assistive technologies.
-
**Tools:**
- axe DevTools: https://www.deque.com/axe/devtools/
- - axe-core API: https://github.com/dequelabs/axe-core/blob/develop/doc/API.md
- WAVE: https://wave.webaim.org/
- WebAIM Contrast Checker: https://webaim.org/resources/contrastchecker/
- **Testing libraries:**
-
- - jest-axe: https://github.com/nickcolley/jest-axe
- - vitest-axe: https://github.com/chaance/vitest-axe
- - cypress-axe: https://github.com/component-driven/cypress-axe
-
- **Screen readers:**
-
- - NVDA Screen Reader: https://www.nvaccess.org/
- - Keyboard Navigation Guide: https://webaim.org/articles/keyboard/
-
---
<critical_reminders>
## CRITICAL REMINDERS
> **All code must follow project conventions in CLAUDE.md** (kebab-case, named exports, import ordering, `import type`, named constants)
**(You MUST ensure all interactive elements are keyboard accessible with visible focus indicators)**
- **(You MUST use Radix UI components for built-in ARIA patterns instead of manual implementation)**
+ **(You MUST use headless component libraries for complex ARIA patterns instead of manual implementation)**
**(You MUST maintain WCAG AA minimum contrast ratios - 4.5:1 for text, 3:1 for UI components)**
**(You MUST never use color alone to convey information - always add icons, text, or patterns)**
**Failure to follow these rules will make the site unusable for keyboard users, screen reader users, and color-blind users - violating WCAG 2.2 Level AA compliance.**
</critical_reminders>