Overview
Build to WCAG 2.2 AA on every public page. The criteria below are the AA floor plus one inexpensive AAA item (reduced motion); treat each as a pass/fail gate.
WCAG 2.2 AA checklist
Run through this list before each release:
- Text contrast >= 4.5:1; large text >= 3:1 (SC 1.4.3)
- UI component borders and focus rings >= 3:1 against adjacent color (SC 1.4.11)
- All interactive controls reachable and operable by keyboard (SC 2.1.1)
- No keyboard traps outside intentional modal dialogs (SC 2.1.2)
- Skip link as first focusable element (SC 2.4.1)
- Focus indicator visible on every interactive element (SC 2.4.7)
- Focus not fully obscured by sticky headers or overlays (SC 2.4.11)
- Images have meaningful
alt; decorative images havealt=""(SC 1.1.1) - Every
<input>has a programmatic label (SC 1.3.1, SC 3.3.2) - Touch and click targets >= 24x24 CSS px, or sufficient spacing (SC 2.5.8)
- Motion animations respect
prefers-reduced-motion(SC 2.3.3; Level AAA, but cheap to meet) - ARIA used only when no native element fits; all ARIA patterns complete
Color contrast: 4.5:1 body, 3:1 large
SC 1.4.3 sets the floor: body text needs 4.5:1 contrast against its background; large text (18pt regular or 14pt bold and above) needs 3:1. SC 1.4.11 extends the rule to interactive component borders and focus rings: they need 3:1 against adjacent colors.
Disabled controls carry no contrast requirement. Disable sparingly; an unusable affordance is worse than a missing one.
Use DevTools color picker or axe-core to confirm. Do not eyeball.
Full keyboard navigation and visible focus
SC 2.1.1, 2.1.2, 2.4.1, 2.4.7, and 2.4.11 cover keyboard operability, focus traps, skip links, focus rings, and focus hidden behind sticky content. The key bindings, DOM order rules, :focus-visible pattern, and skip link markup live in keyboard-accessibility.
Meaningful alt text; empty alt for decorative images
SC 1.1.1 covers all non-text content. The rule is simple:
- Informative image:
altdescribes what the image conveys in context. “Bar chart showing 40% growth in Q2” beats “chart.png”. - Decorative image:
alt=""so screen readers skip it. - Functional image (button icon):
altstates the action. “Search”, not “magnifying glass”. - Complex image (diagram):
altgives a brief label; a longer description follows in adjacent text or a linked page.
Never leave alt missing. Many screen readers fall back to announcing the file name when alt is absent.
Semantic HTML first; ARIA sparingly
Use the element that names the role. Every native element carries keyboard behavior, focus management, and an accessible name at zero cost.
<button>for actions, never<div>or<span>styled as a button.<a href="...">for navigation;<a>withouthrefis not a link.<label>paired with every input; see forms.<fieldset>and<legend>to group radios, checkboxes, and address blocks.<nav>,<main>,<header>,<footer>,<aside>for page landmarks.
ARIA does not add behavior; it changes what assistive tech announces. Use it only when no native element fits: custom comboboxes, live regions (aria-live="polite"), icon-only buttons (aria-label="Close"), disclosure widgets (aria-expanded). A half-built ARIA pattern is worse than a <select>. See html-aria-patterns for complete widget patterns.
Programmatic form labels
SC 1.3.1 and SC 3.3.2 require every input to have a label that is programmatically associated through <label for> or wrapping, not just visually adjacent. A placeholder is never a label. The binding patterns and hint text rules live in forms.
Target size: 24x24 CSS px minimum (SC 2.5.8)
SC 2.5.8 is new in WCAG 2.2. Interactive targets must meet one of:
- Bounding box >= 24x24 CSS px, OR
- Offset spacing around a smaller target such that a 24x24 px circle centered on the target does not intersect any adjacent target.
Inline text links are exempt. Buttons, checkboxes, and icon controls are not. Apply min-width: 24px; min-height: 24px to icon controls rather than relying on the spacing exception.
Respect prefers-reduced-motion (SC 2.3.3)
SC 2.3.3 is a Level AAA criterion: motion animation triggered by interaction can be disabled unless it is essential. Meet it anyway; the cost is one media query. Map this to the CSS media query:
@media (prefers-reduced-motion: reduce) {
*,
*::before,
*::after {
animation-duration: 0.01ms !important;
transition-duration: 0.01ms !important;
scroll-behavior: auto !important;
}
}For JavaScript-driven animations, read window.matchMedia('(prefers-reduced-motion: reduce)').matches and skip or substitute the animation when true.
Automated checking with pa11y
Automated tools catch only part of WCAG failures; many criteria need human judgment. Pair them with one keyboard pass and one screen reader pass per major change; tools miss reading order, focus management, and cognitive load.
Run pa11y against a URL or a local server:
npx pa11y --standard WCAG2AA https://localhost:8080/See run-pa11y-locally for local setup with a dev server and configure-pa11y-thresholds to set error budgets per route in CI.
For Playwright-based pipelines, @axe-core/playwright integrates directly into test files and surfaces violations as assertion failures.