EmpireUI
Get Pro
← Blog9 min read#wcag#accessibility#wcag 2.2

WCAG 2.2 Checklist: Every New Criterion Explained With Code

WCAG 2.2 added 9 new success criteria in 2023. Here's exactly what changed, what it means for your UI code, and how to pass every new test.

developer reviewing accessibility checklist on laptop screen

What Changed in WCAG 2.2

WCAG 2.2 became an official W3C Recommendation on October 5, 2023. That date matters because the EU's European Accessibility Act (EAA), which enforcement began in mid-2025, now references 2.2 as its baseline. If you're shipping a public-facing product, you're almost certainly in scope for something that points at this spec.

The update added 9 new success criteria and removed one (4.1.1 Parsing, which was basically obsolete by the time browsers stabilized their HTML error recovery). The new criteria cluster around three problems the WCAG working group identified from real-world user research: keyboard traps for people using switch access, cognitive load for people with memory and attention challenges, and pointer precision for people who can't hold a mouse steady.

Honestly, most of the new criteria are things good developers were already doing by instinct. But instinct isn't auditable. Having explicit numbered criteria gives you something to point at in a ticket, a design review, or a legal defence. That's the real value of the spec.

Worth noting: WCAG 2.2 is backwards compatible. Every 2.1 criterion still exists (minus 4.1.1). If you already have a 2.1 AA conformance statement, you only need to address the delta — the 9 new SCs — to upgrade it.

2.4.11 Focus Not Obscured (Minimum) — Level AA

This one is straightforward. When a component receives keyboard focus, it can't be entirely hidden behind a sticky header, a cookie banner, or a fixed footer. At least part of the focused element must be visible. The browser scrolls the page, but sticky elements can scroll right over a focused link and leave the user with no visible feedback at all — that's the failure case.

/* Bad: sticky header that covers 100% of focused elements near the top */
header {
  position: sticky;
  top: 0;
  height: 64px;
  z-index: 100;
}

/* Fix: add scroll-padding-top to <html> equal to the sticky header height */
html {
  scroll-padding-top: 80px; /* 64px header + 16px breathing room */
}

The Level AAA version, 2.4.12, goes further: the focused component can't have *any* part obscured, not just fully hidden. AA only requires that it's not completely covered. In practice, scroll-padding-top handles most cases automatically. If you've got nested sticky elements — a site header plus a tab bar — add both heights together.

Quick aside: this criterion doesn't just apply to headers. A fixed chat widget in the bottom-right corner that overlaps a 'Submit' button during form tabbing is an equally real failure. Check all four corners of your viewport when you're keyboard-testing.

2.4.12 Focus Not Obscured (Enhanced) — Level AAA

2.4.12 is the stricter sibling of 2.4.11. At AAA, no part of the focused element can be obscured by author-created content. That's a higher bar and it's Level AAA, so it doesn't block a AA conformance claim. But if you're building components for a design system — like the kind you'd find in Empire UI — shipping to this standard keeps you safe for clients who need AAA or who have particularly demanding auditors.

The implementation strategy is the same as 2.4.11: scroll-padding, generous z-index ordering that deprioritises sticky chrome, and focus management in single-page apps that scrolls the newly focused element into an unobscured zone before handing off keyboard control.

// Utility: scroll an element into view with clearance for fixed chrome
function scrollIntoViewWithOffset(el: HTMLElement, offset = 80) {
  const top = el.getBoundingClientRect().top + window.scrollY - offset;
  window.scrollTo({ top, behavior: 'smooth' });
}

// Use it after programmatic focus
someElement.focus();
scrollIntoViewWithOffset(someElement);

2.5.7 Dragging Movements — Level AA

If your UI has a drag interaction — a sortable list, a range slider, a Kanban board — you need to provide a single-pointer alternative that doesn't require dragging. This is 2.5.7. The target audience is broad: people with tremors, people on switch access, people using sticky keys. Dragging requires continuous pointer contact across a distance, which is a hard physical task for many users.

Look, the fix is rarely a radical redesign. A 'Move up / Move down' button pair beside each draggable item satisfies this criterion. A number input next to a slider satisfies this criterion. You don't have to remove the drag experience — you just can't make it the *only* experience.

// Accessible sortable item: drag AND button fallback
function SortableItem({ item, onMoveUp, onMoveDown }) {
  return (
    <li
      draggable
      onDragStart={/* ... */}
      className="flex items-center gap-3"
    >
      <span className="flex-1">{item.label}</span>
      {/* 2.5.7 single-pointer alternatives */}
      <button
        onClick={() => onMoveUp(item.id)}
        aria-label={`Move ${item.label} up`}
      >
        ↑
      </button>
      <button
        onClick={() => onMoveDown(item.id)}
        aria-label={`Move ${item.label} down`}
      >
        ↓
      </button>
    </li>
  );
}

Range sliders are the trickiest case. A <input type="range"> is already keyboard-operable natively, so that baseline is covered. But custom sliders built with <div> and pointer events need both the drag handle *and* a numeric input or +/- buttons. When you're building sliders for tools like a gradient generator or a box shadow generator, this is non-negotiable — those tools are inherently interactive widgets.

2.5.8 Target Size (Minimum) — Level AA

Touch targets must be at least 24×24 CSS pixels. That's the AA minimum introduced in 2.2. The Level AAA version (2.5.5 from 2.1) still requires 44×44px. If you're building for mobile-first interfaces — and in 2026 you absolutely are — 44px is really where you want to land anyway. The 24px minimum is a floor, not a goal.

The criterion has an exception: if there's at least 24px of spacing between the target and any adjacent target (measured from their nearest edges), you get a pass even if the target itself is smaller. This makes small icon buttons workable as long as they're not crammed together.

/* Ensure minimum 24px target size with spacing exception */
.icon-button {
  min-width: 24px;
  min-height: 24px;
  /* Preferred: 44px for comfortable touch */
  padding: 10px; /* inflates clickable area without inflating visual size */
}

/* Better: use padding to hit 44x44 without changing layout dimensions */
.icon-button {
  font-size: 16px;
  padding: 14px; /* 16 + 28 = 44px total hit area */
  display: inline-flex;
  align-items: center;
  justify-content: center;
}

In practice, the padding trick is your best friend. You can visually show a 20px icon while the actual clickable surface is 44×44px. Your design system tokens should encode this — a --touch-target-min: 44px variable that every interactive component's padding is derived from. Check your button design system tokens against this standard if you haven't already.

One more thing — this criterion also applies to links inside body text. Inline text links are explicitly excluded from 2.5.8 because the spec acknowledges that making every hyperlink 24px tall would break prose layout. But icon-only links (social icons, breadcrumb separators, etc.) are not excluded and need to meet the size requirement.

3.2.6 Consistent Help — Level A

If your site has a help mechanism — a contact link, a chat widget, a support phone number, a FAQ link — and it appears on multiple pages, it has to be in the same *relative* position on every page. That's it. This is a new Level A criterion, which means it's not optional even at the baseline conformance level.

The intent is to support users with cognitive disabilities who build a mental model of where help lives on a page. If your support chat button is bottom-right on the homepage but bottom-left on the account page, you've violated 3.2.6. The position needs to be consistent within a set of pages.

// Pattern: keep help mechanisms in a Layout component
// so position is enforced structurally, not by convention
export function Layout({ children }: { children: React.ReactNode }) {
  return (
    <>
      <Header />
      <main>{children}</main>
      <Footer />
      {/* Help mechanism: always bottom-right, always present */}
      <SupportChat className="fixed bottom-6 right-6 z-50" />
    </>
  );
}

This is why component-level architecture matters for accessibility. If your help widget is placed independently on each page template, you're one commit away from a consistency failure. Lifting it into the shared layout makes 3.2.6 structurally impossible to break.

3.3.7 Redundant Entry — Level A

In a multi-step process, don't ask users to re-enter information they already gave you earlier in the same session. That's 3.3.7. Classic violations: checkout flows that ask for your billing address after you already entered your shipping address (without a 'same as shipping' toggle), or a multi-step form that re-asks for your email on step 3 after you typed it on step 1.

The criterion allows three exceptions: re-entry for security reasons (like typing your password twice during sign-up), when the previously entered data is no longer valid (stale lookup results), and when the step is essential to the task. Outside those exceptions, you either auto-populate the field or give the user a checkbox like 'Same as above'.

// 3.3.7 compliant: pre-fill billing from shipping
function BillingStep({ shippingAddress, onSubmit }) {
  const [sameAsShipping, setSameAsShipping] = useState(true);
  const [billing, setBilling] = useState(shippingAddress);

  return (
    <form onSubmit={onSubmit}>
      <label>
        <input
          type="checkbox"
          checked={sameAsShipping}
          onChange={(e) => {
            setSameAsShipping(e.target.checked);
            if (e.target.checked) setBilling(shippingAddress);
          }}
        />
        Billing address same as shipping
      </label>

      {!sameAsShipping && (
        <AddressFields value={billing} onChange={setBilling} />
      )}
    </form>
  );
}

Worth noting: this criterion applies to the *session*, not just the current page. If a user logs in, navigates away, and comes back, you're not required to remember their inputs. But within a continuous flow — wizard steps, checkout, onboarding sequences — redundant entry is a failure.

3.3.8 and 3.3.9 are the related accessible authentication criteria. 3.3.8 (AA) says you can't require cognitive function tests — puzzles, transcription, calculations — as the only authentication method, unless an alternative is provided. 3.3.9 (AAA) removes the alternative escape hatch entirely. The practical implication: if your login uses a CAPTCHA, you need an audio alternative or a second authentication option that doesn't require solving a visual puzzle.

FAQ

Is WCAG 2.2 backwards compatible with 2.1?

Yes. Everything from 2.1 carries over into 2.2 except 4.1.1 Parsing, which was deprecated. If you already pass 2.1 AA, you only need to address the 9 new 2.2 criteria.

What is the minimum touch target size in WCAG 2.2?

24×24 CSS pixels at AA level (criterion 2.5.8). The AAA requirement from 2.1 (criterion 2.5.5) is 44×44px — still the better target for real-world usability.

Does 2.5.7 mean I have to remove drag-and-drop from my UI?

No. It means you must provide a single-pointer alternative — buttons, a numeric input, a context menu — so dragging isn't the only way to accomplish the task.

When did WCAG 2.2 become enforceable?

It became an official W3C Recommendation in October 2023. The EU's European Accessibility Act, with enforcement starting mid-2025, references 2.2 as its technical baseline for digital products.

Free components in 40 styles
React & Tailwind, copy-paste ready.
Browse →

Read next

WCAG 2025 Accessibility Guide for React DevelopersColor Contrast Tools in 2026: APCA, Contrast Grid, WhocanuseReact Accessibility (a11y): The 8 Patterns You Keep Getting WrongNeumorphism and Accessibility: The Contrast Problem and How to Fix It