Web Accessibility (WCAG 2.2 AA)
Build for everyone — accessibility is a requirement, not a feature.
Contrast Ratios (WCAG AA)
| Element |
Minimum Ratio |
| Normal text (< 18pt) |
4.5:1 |
| Large text (>= 18pt or 14pt bold) |
3:1 |
| UI components and focus indicators |
3:1 |
WCAG 2.2 New AA Criteria
| Criterion |
Requirement |
| Target Size Minimum (2.5.8) |
Interactive targets at least 24x24 CSS pixels |
| Focus Not Obscured Minimum (2.4.11) |
Focused element at least partially visible, not hidden by sticky headers or overlays |
| Focus Appearance (2.4.13) |
Focus indicator has minimum area (2px perimeter) and 3:1 contrast change |
| Dragging Movements (2.5.7) |
Provide single-pointer alternative for any drag interaction |
| Redundant Entry (3.3.7) |
Do not require re-entering previously provided information |
| Consistent Help (3.2.6) |
Help mechanisms (chat, phone, FAQ) appear in same relative order across pages |
| Accessible Authentication (3.3.8) |
No cognitive function test for login (allow paste, autofill, or alternatives) |
Essential Keyboard Patterns
| Key |
Action |
| Tab / Shift+Tab |
Navigate between focusable elements |
| Enter / Space |
Activate buttons and links |
| Arrow keys |
Navigate within widgets (tabs, menus) |
| Escape |
Close dialogs and menus |
| Home / End |
Jump to first/last item in widget |
Element Selection
| Need |
Element |
| Navigates to page |
<a href="..."> |
| Submits form |
<button type="submit"> |
| Opens dialog |
<button aria-haspopup="dialog"> |
| Other action |
<button type="button"> |
| Self-contained article |
<article> |
| Navigation links |
<nav> |
| Supplementary info |
<aside> |
Common ARIA Attributes
| Attribute |
Purpose |
aria-label |
Name when no visible label exists |
aria-labelledby |
Reference existing text as label |
aria-describedby |
Additional description (hints, errors) |
aria-live |
Announce dynamic updates (polite or assertive) |
aria-expanded |
Collapsible/expandable state |
aria-hidden="true" |
Hide decorative elements from screen readers |
aria-invalid |
Mark form fields with errors |
aria-required="true" |
Mark required fields |
aria-busy |
Indicate loading state |
WCAG 2.2 AA Checklist Summary
| Principle |
Key Requirements |
| Perceivable |
Alt text on images, contrast >= 4.5:1, color not sole indicator, text resizable to 200%, captions on video, prefers-reduced-motion support |
| Operable |
Keyboard accessible, visible focus not obscured, focus appearance meets minimum, targets >= 24px, skip links, dragging alternatives |
| Understandable |
<html lang="en">, consistent navigation, consistent help placement, form labels, error identification, no redundant entry, accessible auth |
| Robust |
Valid HTML, name/role/value on all UI components, aria-live for status messages (SC 4.1.1 Parsing is obsolete in WCAG 2.2) |
Anti-Patterns
| Anti-Pattern |
Fix |
<div onClick> instead of <button> |
Use semantic HTML elements |
outline: none without replacement |
Use :focus-visible with visible outline |
| Placeholder as label |
Use <label> element |
tabindex > 0 |
Use DOM order or tabindex="0" / tabindex="-1" |
| Color-only state indicators |
Add icon and text label |
| Skipped heading levels |
Use sequential h1-h6, style with CSS |
role="button" on <button> |
Remove redundant ARIA |
aria-hidden on interactive elements |
Never hide interactive content |
| Fixed font sizes (px) |
Use rem units |
| No focus trap in modal dialogs |
Trap focus, close on Escape |
Common Mistakes
| Mistake |
Correct Pattern |
Adding aria-label to elements that already have visible text |
Use aria-labelledby to reference existing visible text instead |
Using aria-live="assertive" for non-urgent updates |
Use aria-live="polite" for most dynamic content; reserve assertive for errors and alerts |
Setting alt="" on informative images |
Provide descriptive alt text; only use empty alt on purely decorative images |
| Adding keyboard handlers to non-focusable elements |
Use native interactive elements or add tabindex="0" plus role and key handlers |
| Testing only with automated tools like axe |
Automated scans catch ~30% of issues; always supplement with keyboard-only and screen reader testing |
Screen Reader and Browser Pairings
| Screen Reader |
Browser |
Platform |
| JAWS |
Chrome |
Windows |
| NVDA |
Chrome |
Windows (free) |
| NVDA |
Firefox |
Windows (free) |
| VoiceOver |
Safari |
macOS / iOS |
| TalkBack |
Chrome |
Android |
| Narrator |
Edge |
Windows (built-in) |
Testing Quick Guide
| Method |
Tool |
Effort |
| Keyboard-only |
Hide mouse, Tab through page |
5 min |
| Screen reader |
JAWS + Chrome or NVDA + Chrome/Firefox |
10 min |
| Screen reader |
VoiceOver + Safari (macOS) |
10 min |
| Automated scan |
axe DevTools browser extension |
2 min |
| Lighthouse |
Chrome F12 > Lighthouse > Accessibility |
2 min |
| Unit tests |
jest-axe (Jest) or vitest-axe (Vitest) |
Ongoing |
Delegation
When working on accessibility, delegate to:
design-system — Color tokens and contrast verification
react-patterns — Component patterns and hooks
testing — jest-axe integration and test patterns
Resources
References
- Semantic HTML and Structure — Document landmarks, heading hierarchy, element selection, skip links
- Focus Management — Focus indicators, focus not obscured, dialog focus traps, SPA route focus, focus-visible patterns
- ARIA Patterns — Accessible tabs, live regions, data tables with ARIA attributes
- Forms and Validation — Label association, error announcement, redundant entry, accessible authentication
- Color and Media — Contrast requirements, reduced motion, dragging alternatives, video captions, alt text
- Testing — Keyboard testing, screen reader pairings, axe DevTools, jest-axe and vitest-axe unit tests