| name | accessibility |
| description | Use when the user wants to make their app accessible, fix accessibility issues, meet WCAG standards, or support screen readers and keyboard navigation. Also use when the user mentions 'a11y', 'WCAG', 'screen reader', 'keyboard navigation', 'color contrast', 'ARIA', or 'accessibility audit'. |
Accessibility (a11y)
Expert knowledge for building WCAG 2.1 AA-compliant web applications. Accessibility is not a feature — it is a quality dimension that benefits all users.
WCAG 2.1 AA — The Target
4 principles: Perceivable, Operable, Understandable, Robust (POUR)
Level AA compliance is the legal standard in most jurisdictions (EU, Canada, US Section 508) and the minimum for Candidate/Production maturity.
Key AA requirements:
- Color contrast ≥ 4.5:1 for normal text, ≥ 3:1 for large text (≥ 18px regular or ≥ 14px bold)
- All functionality available via keyboard
- No keyboard traps
- Focus visible at all times
- Images have meaningful alt text (or
alt="" for decorative)
- Form inputs have associated labels
- Error messages identify the field and describe the fix
- No content that flashes > 3 times per second
Semantic HTML First
The single highest-leverage accessibility improvement is using correct HTML elements.
<div class="button" onclick="submit()">Submit</div>
<div class="nav"><div class="link">Home</div></div>
<button type="submit">Submit</button>
<nav><a href="/">Home</a></nav>
Rule: If a native HTML element does what you need, use it. ARIA only when native semantics are insufficient.
Semantic landmarks:
<header>
<nav>
<main>
<aside>
<footer>
Keyboard Navigation
Focus management rules:
- Interactive elements (buttons, links, inputs, selects) receive focus natively — don't add
tabindex unless you know why
tabindex="0" makes a non-interactive element focusable (use sparingly)
tabindex="-1" makes an element programmatically focusable but removes it from tab order (use for focus management in modals/drawers)
- Never
tabindex > 0 — breaks natural tab order
Modal / dialog focus trap:
modalRef.current.focus();
triggerRef.current.focus();
Keyboard interactions by component:
| Component | Expected keyboard behavior |
|---|
| Button | Enter or Space activates |
| Link | Enter navigates |
| Dropdown/Select | Arrow keys navigate options, Enter selects, Escape closes |
| Modal | Escape closes, focus trapped inside, focus returns to trigger on close |
| Accordion | Enter/Space toggles, Arrow keys navigate between headers |
| Tab panel | Arrow keys navigate tabs, Enter/Space activates |
Screen Reader Support
Test with real screen readers:
- macOS/iOS: VoiceOver (built-in, free)
- Windows: NVDA (free), JAWS (paid)
- Android: TalkBack
ARIA usage rules:
- No ARIA > bad ARIA. Wrong ARIA is worse than no ARIA.
- Never override native semantics unnecessarily:
<button role="button"> is redundant.
- Always pair
aria-labelledby or aria-label with landmark roles and interactive widgets.
- Use
aria-live regions for dynamic content that updates without page reload.
Common ARIA patterns:
<button aria-label="Close dialog">
<svg aria-hidden="true">...</svg>
</button>
<div aria-live="polite" aria-atomic="true">
</div>
<input aria-describedby="email-error" aria-invalid="true" />
<p id="email-error" role="alert">Enter a valid email address</p>
Color and Visual Design
Contrast checker: Use Colour Contrast Analyser (desktop) or browser DevTools accessibility panel.
Never use color alone to convey information:
<span style="color: red">Error</span>
<span class="error">
<svg aria-hidden="true"></svg>
Error: email is required
</span>
Focus indicator: Default browser focus rings are often removed by CSS resets. Always provide a visible focus style:
:focus-visible {
outline: 2px solid #0066cc;
outline-offset: 2px;
}
Forms
<label for="email">Email address</label>
<input id="email" type="email" autocomplete="email" required />
<fieldset>
<legend>Shipping address</legend>
</fieldset>
<input aria-describedby="email-error" aria-invalid="true" />
<span id="email-error">Enter a valid email address</span>
Don't rely on placeholder text as a label — placeholders disappear on focus and have low contrast.
Motion and Animation
@media (prefers-reduced-motion: reduce) {
*,
*::before,
*::after {
animation-duration: 0.01ms !important;
animation-iteration-count: 1 !important;
transition-duration: 0.01ms !important;
}
}
Honor this media query for all decorative animations. Functional animations (loading spinners) may be kept but slowed.
Audit Tools
| Tool | Type | Use for |
|---|
| axe DevTools (browser extension) | Automated | Catch ~30% of issues fast |
| Lighthouse Accessibility | Automated | CI integration, score tracking |
| VoiceOver / NVDA | Manual | Real screen reader experience |
| Colour Contrast Analyser | Manual | Precise contrast ratios |
| Keyboard-only navigation | Manual | Tab order, focus traps, shortcuts |
Automated tools catch roughly 30-40% of WCAG issues. Manual testing is required for full compliance.
Common Rationalizations
| Rationalization | Reality |
|---|
| "Our users don't use screen readers" | 1 in 4 adults has a disability. Many disabilities are invisible. You can't know your users' needs. |
| "We'll add accessibility later" | Retrofitting is 3-10× more expensive than building accessibly from the start. |
| "ARIA fixes everything" | Incorrect ARIA breaks screen readers. Semantic HTML first, ARIA as last resort. |
| "Color contrast is a design decision" | 4.5:1 is a legal requirement in most jurisdictions, not a preference. |
Verification