Web Accessibility Checklist: WCAG 2.2 Compliance Guide
Web Accessibility Checklist: WCAG 2.2 Compliance Guide
Web Accessibility Checklist: Making Your Site WCAG 2.2 Compliant in 2026
Web accessibility is not just about compliance — it affects real people. Over 1.3 billion people worldwide live with some form of disability, according to the WHO. In the United States alone, the number of web accessibility lawsuits reached 4,605 in 2025, a 12% increase year-over-year. Beyond legal risk, accessible websites consistently see higher engagement, better SEO rankings, and broader audience reach.
WCAG 2.2, published as a W3C Recommendation in October 2023, added 9 new success criteria focused on improving accessibility for users with cognitive and motor disabilities. This comprehensive checklist covers every critical area you need to address, with practical code examples and testing strategies.
The Four WCAG Principles: POUR
WCAG is organized around four principles. Content must be:
- Perceivable — Users can see, hear, or otherwise perceive all content
- Operable — Users can navigate and interact with all interface elements
- Understandable — Users can comprehend the content and interface
- Robust — Content works with current and future assistive technologies
Perceivable: Content Everyone Can Access
Images and Non-Text Content
Every image must have appropriate alternative text. The approach differs based on the image's purpose:
<!-- Informative image: Describe the content -->
<img src="chart.png" alt="Bar chart showing Q4 revenue increased 23% to $4.2M compared to Q3">
<!-- Decorative image: Use empty alt -->
<img src="divider.svg" alt="">
<!-- Functional image (link/button): Describe the action -->
<a href="/home">
<img src="logo.svg" alt="Acme Corp - Go to homepage">
</a>
<!-- Complex image: Provide extended description -->
<figure>
<img src="process-flowchart.png"
alt="Order processing workflow with 6 steps"
aria-describedby="flowchart-desc">
<figcaption id="flowchart-desc">
Step 1: Customer places order. Step 2: Payment verification.
Step 3: Inventory check. Step 4: Packing. Step 5: Shipping.
Step 6: Delivery confirmation sent to customer.
</figcaption>
</figure>
<!-- SVG icons: Always accessible -->
<button>
<svg aria-hidden="true" focusable="false">
<use href="#icon-search"></use>
</svg>
<span>Search</span>
</button>Color and Contrast
WCAG 2.2 Level AA requires minimum contrast ratios:
- Normal text: 4.5:1 contrast ratio against its background
- Large text (18px bold or 24px regular): 3:1 contrast ratio
- UI components and graphical objects: 3:1 contrast ratio
Never rely on color alone to convey information. Add secondary indicators:
/* Bad: Only color differentiates error state */
.error {
color: red;
}
/* Good: Color + icon + text + border */
.error-field {
border: 2px solid #d32f2f;
border-left: 4px solid #d32f2f;
background: #fef2f2;
}
.error-message {
color: #d32f2f;
}
.error-message::before {
content: "\26A0 "; /* Warning triangle */
}Video and Audio Content
- Captions are required for all prerecorded video with audio (Level A)
- Audio descriptions are required for video where visual information is not in the dialogue (Level AA)
- Transcripts should be provided for audio-only content
<video controls>
<source src="demo.mp4" type="video/mp4">
<track kind="captions" src="demo-en.vtt" srclang="en"
label="English captions" default>
<track kind="descriptions" src="demo-desc-en.vtt"
srclang="en" label="English audio descriptions">
<p>Your browser doesn't support video.
<a href="demo-transcript.html">Read the transcript</a>.
</p>
</video>Operable: Interfaces Everyone Can Use
Keyboard Navigation
Every interactive element must be operable with a keyboard alone. Test your entire site using only Tab, Shift+Tab, Enter, Space, Escape, and Arrow keys.
<!-- Custom button: Must be keyboard accessible -->
<!-- Bad: div is not focusable or activatable by keyboard -->
<div onclick="doSomething()">Click me</div>
<!-- Good: Use semantic HTML -->
<button type="button" onclick="doSomething()">Click me</button>
<!-- If you MUST use a non-semantic element: -->
<div role="button" tabindex="0"
onclick="doSomething()"
onkeydown="if(event.key==='Enter'||event.key===' ')doSomething()">
Click me
</div>Focus Management
Focus indicators must be visible and meet WCAG 2.2's enhanced requirements (Success Criterion 2.4.13, Level AAA, but widely recommended at AA):
/* Remove default only if replacing with better indicator */
:focus {
outline: none;
}
/* Custom focus indicator meeting WCAG 2.2 */
:focus-visible {
outline: 3px solid #2563eb;
outline-offset: 2px;
border-radius: 2px;
}
/* Ensure focus is visible on dark backgrounds too */
.dark-section :focus-visible {
outline-color: #93c5fd;
box-shadow: 0 0 0 3px rgba(37, 99, 235, 0.5);
}New in WCAG 2.2: Focus Not Obscured (2.4.11 / 2.4.12)
When an element receives keyboard focus, it must not be fully hidden by sticky headers, footers, or overlapping content. This is a new Level AA criterion:
/* Ensure focused elements scroll into view above sticky header */
.sticky-header {
position: sticky;
top: 0;
z-index: 100;
height: 64px;
}
/* Use scroll-padding to account for sticky header */
html {
scroll-padding-top: 80px; /* Header height + buffer */
}
/* For modals: trap focus inside */
function trapFocus(modal) {
const focusableElements = modal.querySelectorAll(
'button, [href], input, select, textarea, [tabindex]:not([tabindex="-1"])'
);
const firstFocusable = focusableElements[0];
const lastFocusable = focusableElements[focusableElements.length - 1];
modal.addEventListener('keydown', (e) => {
if (e.key !== 'Tab') return;
if (e.shiftKey && document.activeElement === firstFocusable) {
e.preventDefault();
lastFocusable.focus();
} else if (!e.shiftKey && document.activeElement === lastFocusable) {
e.preventDefault();
firstFocusable.focus();
}
});
firstFocusable.focus();
}New in WCAG 2.2: Target Size (2.5.8 Level AA)
Interactive targets must be at least 24x24 CSS pixels, with some exceptions. This is critical for users with motor impairments:
/* Ensure all interactive elements meet minimum target size */
button,
a,
input[type="checkbox"],
input[type="radio"],
select {
min-height: 44px; /* Exceeds the 24px minimum, targets 44px recommendation */
min-width: 44px;
}
/* For inline links in text, spacing provides adequate target */
p a {
min-height: auto;
min-width: auto;
/* Adequate spacing between inline targets is acceptable */
}
/* Touch targets for mobile should be even larger */
@media (pointer: coarse) {
button, a {
min-height: 48px;
min-width: 48px;
padding: 12px 16px;
}
}Understandable: Clear Content and Predictable Behavior
Page Language
Always declare the language of your page and any content in a different language:
<html lang="en">
<body>
<p>This text is in English.</p>
<blockquote lang="fr">
La vie est belle.
</blockquote>
</body>
</html>Form Labels and Error Handling
Every form input must have a visible label. Error messages must be specific and helpful:
<form novalidate>
<div>
<label for="email">Email address <span aria-hidden="true">*</span></label>
<input type="email" id="email" name="email"
required
aria-required="true"
aria-describedby="email-hint email-error"
aria-invalid="false">
<span id="email-hint">We'll never share your email.</span>
<span id="email-error" role="alert" hidden></span>
</div>
<div>
<label for="password">Password <span aria-hidden="true">*</span></label>
<input type="password" id="password" name="password"
required
aria-required="true"
aria-describedby="password-req password-error"
minlength="8">
<span id="password-req">Minimum 8 characters, at least one number and one uppercase letter.</span>
<span id="password-error" role="alert" hidden></span>
</div>
<button type="submit">Create account</button>
</form>
<script>
// Show specific, actionable error messages
function showError(input, message) {
const errorEl = document.getElementById(input.id + '-error');
errorEl.textContent = message;
errorEl.hidden = false;
input.setAttribute('aria-invalid', 'true');
}
// Good error: "Email must include an @ symbol (e.g., [email protected])"
// Bad error: "Invalid input"
</script>New in WCAG 2.2: Redundant Entry (3.3.7 Level A)
Users should not have to re-enter information they already provided in the same process. This is critical for users with cognitive disabilities:
- Auto-populate shipping address from billing address
- Pre-fill form fields from user profile data
- Allow users to select previously entered information
- Carry information forward through multi-step processes
New in WCAG 2.2: Accessible Authentication (3.3.8 Level AA)
Authentication processes must not rely on cognitive function tests (puzzles, memory tasks) unless an alternative is provided:
- Allow password managers — do not block paste in password fields
- Provide alternative login methods — passkeys, magic links, social login alongside CAPTCHA
- If CAPTCHA is required — offer multiple types (image + audio + puzzle) and support third-party solvers
<!-- DO NOT block paste - this fails WCAG 2.2 -->
<!-- Bad: -->
<input type="password" onpaste="return false">
<!-- Good: Allow paste and password managers -->
<input type="password" autocomplete="current-password">
<!-- Provide alternative to CAPTCHA -->
<div>
<!-- Primary: invisible hCaptcha -->
<div id="captcha"></div>
<!-- Alternative: email verification link -->
<p>Having trouble? <a href="/auth/email-verify">Verify via email instead</a>.</p>
</div>Robust: Works With Assistive Technology
Semantic HTML: Your First Line of Defense
Using semantic HTML elements correctly provides the most accessibility for the least effort:
<!-- Use semantic landmarks -->
<header> instead of <div id="header">
<nav> instead of <div id="navigation">
<main> instead of <div id="content">
<aside> instead of <div id="sidebar">
<footer> instead of <div id="footer">
<!-- Correct heading hierarchy -->
<h1>Page Title</h1> <!-- One per page -->
<h2>Section</h2>
<h3>Subsection</h3>
<h3>Subsection</h3>
<h2>Section</h2>
<h3>Subsection</h3>
<!-- Use lists for groups of related items -->
<ul> for unordered lists
<ol> for ordered/sequential lists
<dl> for definition/description listsARIA: When HTML Is Not Enough
Use ARIA attributes only when semantic HTML cannot convey the information. The first rule of ARIA: don't use ARIA if you can use native HTML.
<!-- Tab interface (no native HTML equivalent) -->
<div role="tablist" aria-label="Product information">
<button role="tab" id="tab-1" aria-selected="true"
aria-controls="panel-1">Description</button>
<button role="tab" id="tab-2" aria-selected="false"
aria-controls="panel-2" tabindex="-1">Reviews</button>
<button role="tab" id="tab-3" aria-selected="false"
aria-controls="panel-3" tabindex="-1">Specifications</button>
</div>
<div role="tabpanel" id="panel-1" aria-labelledby="tab-1">
<p>Product description content...</p>
</div>
<div role="tabpanel" id="panel-2" aria-labelledby="tab-2" hidden>
<p>Review content...</p>
</div>
<!-- Live region for dynamic updates -->
<div aria-live="polite" aria-atomic="true">
<!-- Screen reader announces changes here -->
<p>3 items added to cart</p>
</div>Testing Your Accessibility
Automated Testing Tools
Automated tools catch approximately 30-40% of accessibility issues. Use them as a first pass:
- axe DevTools (browser extension) — the industry standard, catches WCAG violations with clear explanations
- Lighthouse Accessibility audit — built into Chrome DevTools
- WAVE (wave.webaim.org) — visual overlay showing issues in context
- Pa11y — CLI tool for CI/CD pipeline integration
# Add Pa11y to your CI pipeline
npm install -D pa11y-ci
# .pa11yci.json configuration
{
"defaults": {
"standard": "WCAG2AA",
"timeout": 10000,
"wait": 1000
},
"urls": [
"http://localhost:3000/",
"http://localhost:3000/products",
"http://localhost:3000/contact"
]
}
# Run in CI
npx pa11y-ciManual Testing Checklist
Automated tools cannot replace manual testing. Perform these checks on every page:
- Keyboard-only navigation: Tab through the entire page. Can you reach and operate every interactive element?
- Screen reader testing: Use NVDA (Windows, free), VoiceOver (macOS/iOS, built-in), or TalkBack (Android, built-in)
- Zoom to 200%: Is all content still visible and functional? No horizontal scrolling required?
- High contrast mode: Enable Windows High Contrast or use
forced-colors: activemedia query - Reduced motion: Enable prefers-reduced-motion and verify animations stop or simplify
/* Respect user's motion preferences */
@media (prefers-reduced-motion: reduce) {
*,
*::before,
*::after {
animation-duration: 0.01ms !important;
animation-iteration-count: 1 !important;
transition-duration: 0.01ms !important;
scroll-behavior: auto !important;
}
}Quick Reference: WCAG 2.2 Compliance Checklist
Use this condensed checklist as a final review before launch:
- All images have appropriate alt text (or empty alt for decorative)
- Color contrast meets 4.5:1 for text, 3:1 for large text and UI components
- All interactive elements are keyboard accessible with visible focus indicators
- Target sizes are at least 24x24 CSS pixels (WCAG 2.2)
- Focus is never obscured by sticky elements (WCAG 2.2)
- Forms have visible labels, specific error messages, and no paste restrictions
- Authentication does not require cognitive tests without alternatives (WCAG 2.2)
- Users don't re-enter previously provided information (WCAG 2.2)
- Page language is declared with
langattribute - Heading hierarchy is logical (h1 → h2 → h3, no skipped levels)
- Landmark regions are used (header, nav, main, footer)
- Videos have captions and audio descriptions
- Animations respect prefers-reduced-motion
- Content reflows at 200% zoom without horizontal scrolling
Accessibility is not a one-time task. Integrate it into your development workflow from the start. Include accessibility criteria in your definition of done, add automated tests to your CI pipeline, and schedule regular manual audits. The result is a better experience for all users, not just those with disabilities.