Web accessibility (a11y) is no longer an afterthought—with the Web Content Accessibility Guidelines (WCAG) 2.2 and strict global compliance mandates (such as the European Accessibility Act and ADA Title II), building accessible web applications is a core engineering requirement.
While automated scanners can catch ~30–40% of accessibility errors (such as missing alt text or failing color contrast), true compliance requires structured manual testing and semantic frontend architecture.
This guide details the WCAG 2.2 criteria, breaks down new 2.2-specific rules, provides clean code patterns, and offers an actionable testing checklist.
1. Conformance Levels & The POUR Framework
WCAG is structured around four foundational principles: Perceivable, Operable, Understandable, and Robust (POUR) across three conformance tiers:
- Level A: Minimum baseline requirement. Failing Level A means basic access is blocked for users with disabilities.
- Level AA (Industry Standard): The standard legal target for enterprise, e-commerce, and public-sector software.
- Level AAA: Highest level of specialized accessibility (e.g., enhanced contrast of 7:1, sign language interpretation).
2. Key New Rules in WCAG 2.2 (Level AA)
WCAG 2.2 introduces critical new success criteria that direct modern UI/UX design:
| Criterion | Level | What it Requires | Common Failure |
|---|---|---|---|
| 2.4.11 Focus Not Obscured (Minimum) | AA | When a component receives keyboard focus, it must not be completely hidden by sticky headers, banners, or floating footers. | Fixed cookie banners or sticky navigation headers covering active input fields when tabbing. |
| 2.5.7 Dragging Movements | AA | Any action achieved via dragging (e.g., drag-and-drop sort, slider) must offer a single-pointer alternative (e.g., buttons, click-to-move). | Kanban boards or reorderable lists with no "Move Up / Move Down" button controls. |
| 2.5.8 Target Size (Minimum) | AA | Clickable/tappable interactive targets must measure at least $24 \times 24\text{px}$ (or have sufficient spacing from adjacent targets). | Tiny icon buttons ($16 \times 16\text{px}$) placed tightly together on mobile screens. |
| 3.3.7 Redundant Entry | A | Information previously entered by the user in a multi-step flow must be auto-populated or available for selection, not re-typed. | Checkout steps demanding billing address re-entry without a "Same as shipping" checkbox. |
| 3.3.8 Accessible Authentication (Minimum) | AA | Authentication cannot rely exclusively on cognitive function tests (memorizing passwords, solving CAPTCHAs) without alternatives. | CAPTCHA-only walls with no WebAuthn, passkey, or copy-paste password support. |
3. Core Accessibility Audit Checklist
A. Color & Contrast Ratios
Under WCAG AA (Criterion 1.4.3 & 1.4.11):
- Normal Body Text (<18pt or <14pt bold): Minimum contrast ratio of 4.5:1 against the background.
- Large Text ($\ge$18pt or $\ge$14pt bold): Minimum contrast ratio of 3:1.
- UI Components & Graphical Objects: Form input borders, active icons, and focus rings require at least 3:1 against adjacent colors.
/* BAD: Fails contrast ratio (2.1:1) */
.muted-caption {
color: #94a3b8; /* light slate */
background-color: #ffffff;
}
/* GOOD: Meets AA standard (4.6:1) */
.accessible-caption {
color: #64748b; /* dark slate */
background-color: #ffffff;
}
B. Keyboard Navigation & Focus Management
All interactive controls must be completely operable using only a keyboard:
- Logical Tab Order: DOM sequence must match the visual layout. Avoid negative
tabindexexcept for programmatic focus management (tabindex="-1"). - Visible Focus Indicators (Criterion 2.4.7): Never remove outline styles without providing an accessible alternative:
/* BAD: Destroys keyboard navigation */ button:focus { outline: none; } /* GOOD: High-visibility accessible focus ring */ button:focus-visible { outline: 2px solid #2563eb; outline-offset: 2px; } - Skip to Main Content Link: Provide a hidden link at the very top of the DOM that becomes visible on focus to allow keyboard users to skip navigation menus.
4. Practical Code Remediations
1. Accessible Icon Buttons
Screen readers cannot infer meaning from an SVG element without semantic text.
<!-- BAD: Screen reader reads nothing or filename -->
<button class="p-2">
<svg class="w-5 h-5"><path d="..." /></svg>
</button>
<!-- GOOD: aria-label with accessible tooltip or sr-only span -->
<button class="p-2" aria-label="Close modal dialog">
<svg class="w-5 h-5" aria-hidden="true" focusable="false">
<path d="..." />
</svg>
<span class="sr-only">Close modal dialog</span>
</button>
2. Form Inputs with Explicit Labels
Placeholder text is not a replacement for a <label> tag—it vanishes on input and often fails contrast criteria.
<!-- BAD: Placeholder as label -->
<input type="email" placeholder="Enter your email" />
<!-- GOOD: Explicit label associated via htmlFor / for -->
<label for="user-email" class="block text-sm font-medium text-gray-700">
Email Address <span aria-hidden="true">*</span>
</label>
<input
id="user-email"
name="email"
type="email"
required
aria-required="true"
aria-describedby="email-hint"
class="mt-1 block w-full rounded-md border-gray-300"
/>
<p id="email-hint" class="text-xs text-gray-500">We'll never share your email.</p>
3. Accessible Modal Dialog (WAI-ARIA Pattern)
A compliant modal must trap keyboard focus within the dialog, lock scroll, and close on the Escape key:
export function AccessibleModal({ isOpen, onClose, title, children }: ModalProps) {
if (!isOpen) return null;
return (
<div
role="dialog"
aria-modal="true"
aria-labelledby="modal-title"
className="fixed inset-0 z-50 flex items-center justify-center bg-black/50"
onKeyDown={(e) => e.key === 'Escape' && onClose()}
>
<div className="bg-white rounded-lg p-6 max-w-lg w-full">
<h2 id="modal-title" className="text-xl font-bold">{title}</h2>
<div className="mt-4">{children}</div>
<button
onClick={onClose}
className="mt-6 px-4 py-2 bg-blue-600 text-white rounded hover:bg-blue-700 focus-visible:ring-2"
>
Confirm & Close
</button>
</div>
</div>
);
}
5. Automated CI/CD & Audit Workflow
Combine automated checks with manual verification:
- Linting: Integrate
eslint-plugin-jsx-a11yinto your build to catch unassigned labels, non-interactive elements with click handlers, and missingaltattributes. - Automated Testing: Run Playwright or Cypress with
@axe-core/playwrighton all critical user journeys. - Manual Keyboard Pass: Unplug your mouse and verify you can tab through, activate, and dismiss every interactive element.
- Screen Reader Verification: Test core workflows with VoiceOver (macOS / iOS) or NVDA (Windows).
Run your markup through the A11y Accessibility Checker and verify palette contrast with the Color Converter to ensure full WCAG 2.2 AA compliance.