WCAG 2.2 became a W3C Recommendation on October 5, 2023, and it's the version most procurement teams now reference in RFPs and most regulators reference in enforcement. It adds nine new success criteria on top of WCAG 2.1, removes one (4.1.1 Parsing — declared obsolete because modern browsers handle malformed HTML predictably), and keeps the rest unchanged.
Most "WCAG checklist" articles either restate the spec verbatim or hide the real implementation detail behind a paywall. This one gives you the actual HTML, CSS, and ARIA patterns we use to satisfy each criterion when we ship a static template. If you can't get to the underlying markup of your site, almost none of this will help — accessibility is a markup problem, not a plugin problem.
Scope: this is the AA-level checklist. AAA is rarely required by procurement and the cost-benefit gets steep. The nine new criteria added in 2.2 are flagged with a 2.2 NEW tag.
What actually changed in WCAG 2.2
Per the official W3C recommendation, the nine new criteria are:
- 2.4.11 Focus Not Obscured (Minimum) — AA
- 2.4.12 Focus Not Obscured (Enhanced) — AAA
- 2.4.13 Focus Appearance — AAA
- 2.5.7 Dragging Movements — AA
- 2.5.8 Target Size (Minimum) — AA
- 3.2.6 Consistent Help — A
- 3.3.7 Redundant Entry — A
- 3.3.8 Accessible Authentication (Minimum) — AA
- 3.3.9 Accessible Authentication (Enhanced) — AAA
Six of those nine are at A or AA level. They're enforceable. The pattern across the new criteria is interesting: WCAG 2.2 is no longer about page structure — it's about interaction. Sticky headers covering focus rings, drag-only sortable lists, 24×24px tap targets, "remember me" for authentication. These are the things real users actually trip over.
One removal worth noting: 4.1.1 Parsing was deleted. You can stop running HTML validators in your accessibility CI pipeline. The modern browser parser is forgiving enough that malformed HTML doesn't break assistive tech in practice.
Perceivable — text alternatives and structure
Every meaningful image has descriptive alt text
Alt text describes the function or content of the image, not the file. Decorative images get alt="" (empty, not missing).
<!-- functional -->
<img src="/icons/cart.svg" alt="Shopping cart, 3 items">
<!-- decorative -->
<img src="/decoration/swoop.svg" alt="" role="presentation">
<!-- complex (chart, infographic) -->
<img src="/q3-revenue.png" alt="Q3 revenue chart"
longdesc="#q3-data-table">
Use semantic landmarks, not div soup
One <main>, one <header>, one <nav>, one <footer> per page. Multiple navs get an aria-label. Section headings nest in order — never skip h2 to h4.
<header>…</header>
<nav aria-label="Main navigation">…</nav>
<main id="main">
<article>
<h1>Page title</h1>
<section><h2>…</h2></section>
</article>
</main>
<footer>…</footer>
Add autocomplete tokens to form fields
Browsers and assistive tools use the autocomplete attribute to fill fields, surface them at the OS level, and announce them to screen readers. The HTML spec defines the full token list.
<input type="email" name="email" autocomplete="email">
<input type="text" name="given" autocomplete="given-name">
<input type="text" name="family" autocomplete="family-name">
<input type="tel" name="phone" autocomplete="tel">
<input type="text" name="postal" autocomplete="postal-code">
Body text 4.5:1, large text 3:1
Verify with axe DevTools or the browser's contrast checker, not your eyes. The most common dark-theme failure: #9ca3af on #0a0a0b hits 5.4:1 — passes. #737373 on #0a0a0b hits 3.4:1 — fails for body text.
No horizontal scrolling at 320 CSS pixels
Test at 1280px wide and 400% zoom (which simulates a 320px viewport at 100% zoom). Anything that scrolls horizontally fails — except data tables and code blocks, which are exempt by the spec.
/* Avoid this; min-width forces horizontal scroll */
.bad { min-width: 600px; }
/* Prefer */
.good { width: 100%; max-width: 600px; }
UI controls and graphical info: 3:1 contrast
Button borders, focus rings, checkbox outlines, icon-only buttons, chart legends, status dots — all 3:1 against their background. The most common failure is a 1px gray border on a white card.
Layout survives spacing overrides
Users with low vision use bookmarklets to enforce: line-height ≥ 1.5×, paragraph spacing ≥ 2×, letter-spacing ≥ 0.12×, word-spacing ≥ 0.16×. If your layout breaks (text clipped, overlapping containers), you fail.
Tooltips must be dismissible, hoverable, persistent
If you show content on hover (a tooltip, a popover), it must be dismissable with Esc, the user must be able to move their pointer over the tooltip without it disappearing, and it must stay visible until they move away or dismiss it.
Operable — keyboard, timing, navigation
Every interaction reachable via keyboard alone
Tab through your site. If something requires a mouse, you fail. The most common offender: custom dropdowns built from div elements with onclick. Use a <button> or a real <select>.
Focus can always escape
The classic failure: a modal that traps focus inside but doesn't expose Esc to close. Either close on Esc, or include a focusable close button as the last tab stop, or both.
document.addEventListener('keydown', (e) => {
if (e.key === 'Escape' && modal.open) modal.close();
});
Skip-to-content link
First focusable element on the page. Visible only on focus. Jumps to #main.
<a href="#main"
class="sr-only focus:not-sr-only
focus:fixed focus:top-3 focus:left-3
focus:z-50 focus:px-4 focus:py-2
focus:bg-accent focus:text-accent-fg">
Skip to main content
</a>
Visible focus ring on every interactive element
Default browser outlines are fine. Stripping them with outline: none without replacement is the single most common WCAG failure on production sites. If you want a custom ring, replace one for one:
:focus-visible {
outline: 2px solid var(--accent);
outline-offset: 2px;
}
Sticky headers must not cover the focused element
The number-one source of new 2.2 failures. Tabbing down the page hits an input that scrolls under your fixed header and the user can't see what they've focused. Use scroll-padding-top on the root to reserve space:
html {
scroll-padding-top: 80px; /* match header height */
}
Drag interactions must have a non-drag alternative
Sortable lists, color pickers, sliders, map panning — all must be operable without dragging. The simplest fix: add up/down arrow buttons next to draggable items, or expose keyboard arrow-key reordering on focus.
Touch targets ≥ 24×24 CSS pixels
This is the new floor. Apple's HIG recommends 44pt and Google's Material recommends 48dp — both more generous. The 24px floor exists for inline links inside paragraphs (which are exempt anyway) and density-sensitive UI like calendar grids. For primary CTAs, stay at 44px+.
.btn { min-height: 44px; min-width: 44px; }
input { min-height: 44px; }
Understandable — language, predictability, input
Set lang on the html element
<html lang="en">
Without this, screen readers default to the user's preferred voice and read English content with the wrong phoneme set. Trivial to add, frequently missed.
Help mechanisms appear in the same place across pages
If your support widget is bottom-right on the homepage, it has to be bottom-right on every page that has one. The same applies to the contact link in the footer, the help search in the header, etc. Pick a position per mechanism and keep it.
Errors are announced, not just colored red
Wrap the field in something with aria-invalid="true" and reference an error message via aria-describedby. Don't rely on red border alone — color isn't enough per SC 1.4.1.
<label for="email">Email</label>
<input type="email" id="email"
aria-invalid="true"
aria-describedby="email-err">
<p id="email-err" class="error">
Email is required.
</p>
Don't ask for the same info twice in one flow
Multi-step checkouts that ask for the email at step 1 and again at step 3 fail. Either auto-fill, persist between steps, or include a "same as billing" toggle for shipping addresses.
No cognitive function tests for login
Solving a CAPTCHA, transcribing a string, remembering a password — all are "cognitive function tests" under 2.2. To pass at AA, provide an alternative: passkeys, magic link via email, or browser-managed passwords (which is why autocomplete="current-password" matters).
<input type="password"
autocomplete="current-password"
name="password"
required>
Your password manager fills it; you don't have to remember it. That's the alternative.
Robust — name, role, value
Custom widgets need ARIA, not just CSS
If you build a custom dropdown, toggle, accordion, or tab set, you need to declare its role, expose its current state, and update both as the user interacts. The five most common patterns:
| Widget | Role + state |
|---|---|
| Toggle | role="switch" aria-checked="true|false" |
| Accordion header | aria-expanded="true|false" aria-controls="panel-id" |
| Tab | role="tab" aria-selected="true|false" |
| Modal dialog | role="dialog" aria-modal="true" aria-labelledby="title-id" |
| Live region | aria-live="polite|assertive" |
For most of these, use <details>/<summary> or <dialog> instead of building from scratch — they ship with the right semantics and behavior.
Toast notifications need a live region
If a status appears on screen without focus moving to it, a screen reader user won't hear it unless it's announced via aria-live. Polite for non-urgent ("saved"), assertive for blocking errors ("connection lost").
<div role="status" aria-live="polite">
Settings saved.
</div>
The minimum CI checks worth running
Per the W3C and confirmed by the most-cited public study (Deque's analysis of axe-core coverage), automated tests catch a fraction of WCAG issues — the rest require manual review. We covered the gap in the 43% scanners miss. But the automatable share is genuinely worth catching in CI:
# in CI (GitHub Actions, etc.)
npx @axe-core/cli https://staging.yourdomain.com \
--tags wcag2a,wcag2aa,wcag22aa \
--exit
# in dev (Chrome DevTools)
# Lighthouse → Accessibility → Run audit
# in browser (visual)
# WAVE extension → annotate page in place
Add the wcag22aa tag explicitly. Not every scanner has the new rules turned on by default yet.
What you still have to test by hand
Six things no automated tool will catch reliably:
- Keyboard navigation flow. Tab through the page from the top. Does focus move in a logical order? Does it ever land on something invisible?
- Screen reader announcement quality. Run NVDA on Windows or VoiceOver on macOS. Does each landmark announce correctly? Are images skipped or read appropriately?
- Focus appearance under sticky elements. The new 2.4.11 SC. Tab down a long page with a sticky header — does focus stay visible?
- Drag alternatives. Try every sortable, slider, and map control with arrow keys only.
- Tap target spacing on real devices. 24×24 in a CSS audit can still be cramped on a small phone.
- Cognitive load on auth screens. Can you log in with a password manager only? If you have to read a CAPTCHA aloud, you fail 3.3.8.
You should buy AccessiWeb if…
AccessiWeb ($59) is a single-file HTML template that ships with every pattern in this checklist already wired up — skip links, semantic landmarks, visible focus rings with offsets, scroll-padding for sticky headers, 44px touch targets, autocomplete tokens on every form field, ARIA on every custom widget, a reduced-motion media query, and a high-contrast theme override.
It's the right purchase if:
- You're starting a new marketing site and want to ship something that passes a procurement accessibility review the day it goes live.
- You're using a template that fails axe-core scans and you want to copy patterns from one that doesn't.
- You're bidding on government, healthcare, or higher-ed work where ADA compliance is in the contract.
- You want a reference codebase to point developers at when they ask "how do I do focus rings the accessible way."
It's not a substitute for manual testing, an audit, or hiring an accessibility specialist for high-risk surfaces. It's the floor, not the ceiling.
Related reading: The 43% of WCAG issues automated scanners miss · Free website audit tools and what they miss · The 2026 SaaS landing page checklist