Skip to main content

Accessibility · 2026-05-06 · 16 min read

WCAG 2.2 Implementation Checklist for HTML (Plain Markup)

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

SC 1.1.1 · Level A · Non-text content

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">

SC 1.3.1 · Level A · Info and relationships

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>

SC 1.3.5 · Level AA · Identify input purpose

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">

SC 1.4.3 · Level AA · Contrast (Minimum)

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.

SC 1.4.10 · Level AA · Reflow

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; }

SC 1.4.11 · Level AA · Non-text contrast

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.

SC 1.4.12 · Level AA · Text spacing

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.

SC 1.4.13 · Level AA · Content on hover or focus

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

SC 2.1.1 · Level A · Keyboard

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>.

SC 2.1.2 · Level A · No keyboard trap

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();
});

SC 2.4.1 · Level A · Bypass blocks

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>

SC 2.4.7 · Level AA · Focus visible

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;
}

SC 2.4.11 · Level AA · Focus Not Obscured (Minimum) · 2.2 NEW

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 */
}

SC 2.5.7 · Level AA · Dragging Movements · 2.2 NEW

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.

SC 2.5.8 · Level AA · Target Size (Minimum) · 2.2 NEW

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

SC 3.1.1 · Level A · Language of page

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.

SC 3.2.6 · Level A · Consistent Help · 2.2 NEW

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.

SC 3.3.1 · Level A · Error identification

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>

SC 3.3.7 · Level A · Redundant Entry · 2.2 NEW

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.

SC 3.3.8 · Level AA · Accessible Authentication (Minimum) · 2.2 NEW

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

SC 4.1.2 · Level A · 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:

WidgetRole + state
Togglerole="switch" aria-checked="true|false"
Accordion headeraria-expanded="true|false" aria-controls="panel-id"
Tabrole="tab" aria-selected="true|false"
Modal dialogrole="dialog" aria-modal="true" aria-labelledby="title-id"
Live regionaria-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.

SC 4.1.3 · Level AA · Status messages

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:

  1. Keyboard navigation flow. Tab through the page from the top. Does focus move in a logical order? Does it ever land on something invisible?
  2. Screen reader announcement quality. Run NVDA on Windows or VoiceOver on macOS. Does each landmark announce correctly? Are images skipped or read appropriately?
  3. Focus appearance under sticky elements. The new 2.4.11 SC. Tab down a long page with a sticky header — does focus stay visible?
  4. Drag alternatives. Try every sortable, slider, and map control with arrow keys only.
  5. Tap target spacing on real devices. 24×24 in a CSS audit can still be cramped on a small phone.
  6. 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

Newsletter

Get future posts + new templates

Occasional (monthly-ish) notes on what we've shipped + conversion experiments. Unsubscribe any time.