web-accessibility-web-accessibility · git:20260202.b236384 · 2026-02-02 · sha256 64580a2e88d98e1f
web-accessibility-web-accessibility git:20260202.b236384A
Immutable. This exact content is served forever at /api/v1/blob/64580a2e88d98e1f.
---
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.
---
<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 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
**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)
- Adding dynamic content updates (live regions, status messages)
**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.
Key philosophy:
- **Semantic HTML first** - Use native elements for built-in accessibility
- **Radix UI for complex patterns** - Leverage tested, accessible component primitives
- **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
- **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**
- 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.)
---
### ARIA with Radix UI
**Prefer Radix UI components** - They handle ARIA automatically:
- `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
#### 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
---
### 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
- 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
---
### 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)
---
### 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)
- 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
---
### 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:**
- `aria-hidden="true"` for decorative content
- Empty `alt=""` for decorative images
---
### 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:**
```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 */
@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:
**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
**Note:** axe-core finds ~57% of WCAG issues automatically. Always combine with manual testing.
### Manual Testing Checklist
**Keyboard navigation:**
- [ ] 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:**
- [ ] 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:**
- [ ] 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)
- [ ] Focus indicators have 3:1 contrast
- [ ] Animations respect prefers-reduced-motion
### Screen Reader Testing
**Test with multiple screen readers:**
- **NVDA** (Windows) - Free, most popular
- **JAWS** (Windows) - Industry standard
- **VoiceOver** (macOS/iOS) - Built-in
- **TalkBack** (Android) - Built-in
</testing>
---
## 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 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>