WCAG Checklist
Audit the app against the four WCAG principles: Perceivable, Operable, Understandable, Robust.
Why
WCAG (Web Content Accessibility Guidelines) is the international standard for accessibility, and it is the law in many jurisdictions — the ADA in the US, the EAA in the EU (effective 2025), and equivalent laws in 30+ countries. Beyond compliance, accessibility is usability for 15% of the population who have a disability: the blind user with a screen reader, the motor-impaired user with a keyboard, the low-vision user with a screen magnifier, the deaf user with captions. A form that cannot be filled with a keyboard, an image without alt text, a button with no label — these exclude users and invite lawsuits. The WCAG checklist is the systematic way to find and fix these barriers, and it is the baseline for an app that works for everyone.
When
From the design phase through every release. Run automated checks in CI, manual checks before launch, and a full audit annually or when a major redesign ships.
How
1. Run automated checks as a first pass - axe-core (or @axe-core/playwright) for programmatic checks - Lighthouse Accessibility audit for a quick score - eslint-plugin-jsx-a11y for lint-time checks in React - These catch ~30% of WCAG issues — the mechanical ones (missing alt, no label) 2. Manual keyboard testing (the most important manual check) - Put away the mouse, navigate the entire app with Tab, Shift+Tab, Enter, Space, Escape - Can you reach every interactive element? Is the focus order logical? - Is the focus indicator visible? Can you activate buttons and links? - Can you close modals with Escape, and does focus return to the trigger? 3. Screen reader testing (the second most important) - Use NVDA (free) or JAWS on Windows, VoiceOver on Mac, TalkBack on Android - Navigate by heading (H key), by form (F), by link (K), by region (R) - Does the page have a logical heading structure (one h1, then h2s, then h3s)? - Are form fields announced with their labels? Are errors announced? - Are dynamic changes announced (aria-live for toasts, cart updates, errors)? 4. Check the four WCAG principles (POUR) - Perceivable: content is presentable to all senses (alt text, captions, sufficient contrast) - Operable: interface is navigable by all inputs (keyboard, no time limits, no traps) - Understandable: content and operation are comprehensible (clear language, error recovery) - Robust: content works with assistive tech (valid HTML, ARIA used correctly) 5. Document and track findings - File each finding as a ticket with the WCAG criterion it violates (e.g., "1.4.3 Contrast") - Prioritize by impact: Level A (must fix) > AA (should fix) > AAA (nice to have) - Most legal requirements are Level AA — target AA as the baseline
Example
// Automated: axe-core in Playwright
import { expect } from "@playwright/test";
import AxeBuilder from "@axe-core/playwright";
test("home page has no WCAG violations", async ({ page }) => {
await page.goto("/");
const results = await new AxeBuilder({ page })
.withTags(["wcag2a", "wcag2aa"]) // Level A and AA
.analyze();
expect(results.violations).toEqual([]);
});
// Lint: eslint-plugin-jsx-a11y catches issues at write time
// .eslintrc.json
{
"extends": ["plugin:jsx-a11y/recommended"]
}
// Catches: <div onClick> without role, <img> without alt, <input> without label
// Manual: keyboard test checklist
// 1. Tab through the page — is every interactive element reachable?
// 2. Is the focus indicator visible (not outline: none)?
// 3. Can you open the modal, interact, and close with Escape?
// 4. Does focus return to the button that opened the modal?
// 5. Is the tab order logical (follows the visual order)?
// Screen reader: NVDA + Chrome
// 1. Press H to navigate by headings — is the structure logical?
// 2. Press F to navigate by forms — are fields announced with labels?
// 3. Submit a form with an error — is the error announced?
// 4. Add to cart — is the cart update announced (aria-live)?
// Fix: add alt text, labels, and a logical heading structure
// Before:
<div onClick={addToCart}>Buy</div>
<img src="/product.jpg" />
// After:
<button onClick={addToCart} aria-label="Add product to cart">Buy</button>
<img src="/product.jpg" alt="Blue widget, 5cm x 5cm, metal" />Common Problems
- ⚠Running only axe-core and treating zero violations as "accessible" — axe catches the mechanical issues (missing alt, no label) but misses the semantic ones (heading order, link text, error recovery), which require manual review.
- ⚠Adding aria-label to every element "to be safe" — redundant labels override the visible text and make the screen reader output worse, not better (aria-label should supplement, not duplicate).
- ⚠Testing with a screen reader once, finding it overwhelming, and giving up — screen reader proficiency takes practice, but even 30 minutes of testing reveals the major barriers that affect real users.
- ⚠Designing the visual UI first and "adding accessibility later" — by the time the design is built, the heading structure, focus order, and contrast are baked in, and retrofitting is far harder than designing for a11y from the start.
Best Practices
- ✓Combine automated checks (axe in CI, jsx-a11y in lint) with manual keyboard and screen reader testing — automated catches 30%, manual catches the semantic issues that affect real users.
- ✓Design for accessibility from the start: logical heading structure, sufficient contrast, visible focus indicators, keyboard operability — retrofitting is 10x harder than designing in.
- ✓Test with real assistive technology (NVDA, VoiceOver) on real devices — emulators and simulators do not fully replicate the experience, and the real test is whether a screen reader user can complete the task.
- ✓Target WCAG 2.1 Level AA as the baseline (it is the legal requirement in most jurisdictions) and track each criterion as a checklist item with a status and an owner.