WCAG 2.2 Checklist: All 9 New Criteria + the Full AA List

WCAG 2.2 checklist: the 9 new success criteria explained, all 55 Level A and AA criteria in one table, what automation can test, and real failure data.

BugViso

15 min read

WCAG 2.2 became a W3C Recommendation in October 2023. It adds nine new success criteria to WCAG 2.1 and removes one (4.1.1 Parsing). To conform at Level AA you must meet all 55 Level A and AA criteria. Six of those are new: Focus Not Obscured (Minimum), Dragging Movements, Target Size (Minimum), Consistent Help, Redundant Entry and Accessible Authentication (Minimum).

Below: each new criterion with a pass/fail example and a test method, then all 55 A/AA criteria in one table, marked by what automation can check. Of the six new A/AA criteria, a standard automated engine fully tests only one.

We also measured two of the new criteria on real sites. 24.7% of 219 randomly sampled homepages failed Target Size (Minimum) in axe-core, and 34.6% of 208 homepages had at least one link or button completely hidden behind a sticky element while it had keyboard focus.


What Changed From WCAG 2.1 to 2.2

CriterionLevelIn one sentenceAutomated testing
2.4.11 Focus Not Obscured (Minimum)AAA focused element must not be entirely hidden by author contentScriptable (see below)
2.4.12 Focus Not Obscured (Enhanced)AAANo part of the focused element may be hiddenScriptable
2.4.13 Focus AppearanceAAAFocus indicator must be large and high-contrast enoughPartial
2.5.7 Dragging MovementsAAAnything done by dragging must also work with single clicks or tapsManual
2.5.8 Target Size (Minimum)AAPointer targets at least 24Γ—24 CSS px, or enough spacingβœ… axe target-size
3.2.6 Consistent HelpAHelp links or contact details appear in the same relative place on each pageManual
3.3.7 Redundant EntryADon't make users re-type information they already gave in the same processManual
3.3.8 Accessible Authentication (Minimum)AANo cognitive test (memorising, transcribing, puzzles) to log in, unless an alternative existsManual
3.3.9 Accessible Authentication (Enhanced)AAAAs 3.3.8, without the object-recognition exceptionsManual
4.1.1 ParsingARemoved: modern browsers and assistive tech no longer depend on itβ€”

See the W3C's What's New in WCAG 2.2 and the full WCAG 2.2 Recommendation.

πŸ’‘ Backwards compatible: conforming to WCAG 2.2 AA also satisfies 2.1 AA, which most current laws and procurement rules still cite.


Tier 1 (P0): The Six New A/AA Criteria, One by One

2.5.8 Target Size (Minimum), AA

Every pointer target (link, button, form control) must be at least 24Γ—24 CSS pixels. A smaller target still passes if a 24px-diameter circle centred on it doesn't overlap another target or that target's circle (the spacing exception). Links inside a sentence are exempt, as are targets whose size the browser controls and targets where the small size is essential.

css
/* ❌ 16px social icons packed 4px apart: fails 2.5.8 */
.social a { width: 16px; height: 16px; margin-right: 4px; }

/* βœ… Visual icon stays 16px; the hit area grows to 24px+ */
.social a { display: inline-grid; place-items: center; min-width: 24px; min-height: 24px; }
.social a img { width: 16px; height: 16px; }

In our sample, 54 of 219 homepages (24.7%) failed axe-core's target-size rule (1,038 elements), mostly social icons, carousel dots, banner close buttons and packed footer links. 24px is the AA minimum. The AAA criterion 2.5.5 and most touch guidelines recommend 44px.

2.4.11 Focus Not Obscured (Minimum), AA

When an element receives keyboard focus, it must not be completely hidden by content you added: sticky headers, cookie banners, chat launchers, "back to top" buttons. Partly covered still passes AA. Fully covered fails.

The classic failure needs Shift+Tab. Tabbing forward scrolls the next element in at the bottom of the viewport. Tabbing backward scrolls it in at the top, right under a sticky header.

css
/* βœ… Make the browser keep focused elements clear of a 72px sticky header */
html { scroll-padding-top: 80px; }

/* βœ… Same idea for a fixed bottom cookie bar */
html { scroll-padding-bottom: 96px; }

scroll-padding keeps focused elements from landing underneath the bar. We tested this on 208 homepages with 40 Tab and 40 Shift+Tab presses each, without dismissing any banner. 72 (34.6%) had at least one focused element fully hidden. The covering element was a sticky header, nav bar or other fixed element on 48 homepages (23.1%), a cookie or consent banner on 21 (10.1%), and a pop-up modal on 13 (6.2%). We only counted cases where the covering element was itself fixed or sticky, so these figures are conservative.

2.5.7 Dragging Movements, AA

Sliders, sortable lists, map panning and kanban boards need a single-pointer alternative to dragging: buttons, a number input, or a "Move to…" menu.

html
<!-- βœ… +/- buttons for users who can't drag precisely -->
<button type="button" aria-label="Decrease price">βˆ’</button>
<input type="range" min="0" max="500" step="10" aria-label="Maximum price">
<button type="button" aria-label="Increase price">+</button>

3.2.6 Consistent Help, A

If you offer help (contact link, phone, chat, FAQ) on multiple pages, keep it in the same relative order each time, typically a fixed slot in the header or footer. You don't have to offer help, only be consistent when you do.

3.3.7 Redundant Entry, A

Within one process, don't make users type the same information twice; pre-fill it or offer reuse ("Billing address same as shipping"). Re-entry for security, like confirming a new password, is exempt.

3.3.8 Accessible Authentication (Minimum), AA

Logging in must not depend on a cognitive function test, such as remembering a password, transcribing a code or solving a puzzle, unless you provide an alternative or assistance. Two things practically guarantee a pass:

html
<!-- βœ… Let password managers and paste work: never block them -->
<input type="email" name="email" autocomplete="username">
<input type="password" name="password" autocomplete="current-password">
<!-- ❌ onpaste="return false" on either field fails 3.3.8 -->

Passkeys, magic links and "sign in with" providers also pass. Text-transcription CAPTCHAs fail without an alternative; object-recognition CAPTCHAs are allowed at AA but not AAA.


Tier 2 (P1): The Complete WCAG 2.2 Level A and AA Checklist

All 55 criteria you must meet for 2.2 AA. Auto means engines like axe-core have rules that can find failures, though passing them doesn't prove conformance. Partial means automation catches some failure types. Manual means a person has to test it.

SCNameLevelTesting
1.1.1Non-text ContentAPartial
1.2.1Audio-only and Video-only (Prerecorded)AManual
1.2.2Captions (Prerecorded)AManual
1.2.3Audio Description or Media AlternativeAManual
1.2.4Captions (Live)AAManual
1.2.5Audio Description (Prerecorded)AAManual
1.3.1Info and RelationshipsAPartial
1.3.2Meaningful SequenceAManual
1.3.3Sensory CharacteristicsAManual
1.3.4OrientationAAManual
1.3.5Identify Input PurposeAAPartial
1.4.1Use of ColorAPartial
1.4.2Audio ControlAManual
1.4.3Contrast (Minimum)AAAuto
1.4.4Resize TextAAPartial
1.4.5Images of TextAAManual
1.4.10ReflowAAManual
1.4.11Non-text ContrastAAManual
1.4.12Text SpacingAAPartial
1.4.13Content on Hover or FocusAAManual
2.1.1KeyboardAPartial
2.1.2No Keyboard TrapAManual
2.1.4Character Key ShortcutsAManual
2.2.1Timing AdjustableAPartial
2.2.2Pause, Stop, HideAManual
2.3.1Three Flashes or Below ThresholdAManual
2.4.1Bypass BlocksAPartial
2.4.2Page TitledAPartial
2.4.3Focus OrderAManual
2.4.4Link Purpose (In Context)APartial
2.4.5Multiple WaysAAManual
2.4.6Headings and LabelsAAManual
2.4.7Focus VisibleAAManual
2.4.11Focus Not Obscured (Minimum) β€” newAAScriptable
2.5.1Pointer GesturesAManual
2.5.2Pointer CancellationAManual
2.5.3Label in NameAManual
2.5.4Motion ActuationAManual
2.5.7Dragging Movements β€” newAAManual
2.5.8Target Size (Minimum) β€” newAAAuto
3.1.1Language of PageAAuto
3.1.2Language of PartsAAPartial
3.2.1On FocusAManual
3.2.2On InputAManual
3.2.3Consistent NavigationAAManual
3.2.4Consistent IdentificationAAManual
3.2.6Consistent Help β€” newAManual
3.3.1Error IdentificationAManual
3.3.2Labels or InstructionsAPartial
3.3.3Error SuggestionAAManual
3.3.4Error Prevention (Legal, Financial, Data)AAManual
3.3.7Redundant Entry β€” newAManual
3.3.8Accessible Authentication (Minimum) β€” newAAManual
4.1.2Name, Role, ValueAPartial
4.1.3Status MessagesAAManual

Count the right-hand column and the gap is obvious: 3 criteria are Auto, 14 are Partial, 1 is Scriptable, and 37 (two-thirds) are Manual. Our guide to what automated accessibility testing catches and what it misses covers the full split rule by rule.


Tier 3 (P2): Where Real Sites Fail

The new criteria get the attention, but sites mostly fail the old ones. Across our 219 homepages, 87.7% failed at least one rule, led by color-contrast (64.4%), link-name (42.5%), image-alt and the new target-size (24.7% each). Fix those first. Our website accessibility statistics for 2026 have the full breakdown, and our walkthroughs on fixing color contrast errors and the most common WCAG violations cover the fixes.


Validation Protocol: Test the Two Scriptable 2.2 Criteria

This script checks 2.5.8 (with the spacing and inline-link exceptions) and 2.4.11 (tabbing forward and backward). It's the same test we ran on the sample above.

python
#!/usr/bin/env python3
"""WCAG 2.2 checks: SC 2.5.8 Target Size (24px or spacing exception; inline links exempt)
and SC 2.4.11 Focus Not Obscured (Tab + Shift+Tab, flag fully hidden focused elements).

Usage:  pip install playwright && playwright install chromium
        python3 wcag22_check.py https://example.com --tabs 60
"""
import argparse, asyncio
from playwright.async_api import async_playwright

TARGETS_JS = r"""() => {
  const sel = 'a[href], button, input:not([type=hidden]), select, textarea, [role=button], [role=link], [role=checkbox], [role=tab], [onclick]';
  const els = [...document.querySelectorAll(sel)].filter(el => {
    const r = el.getBoundingClientRect(), s = getComputedStyle(el);
    return r.width > 1 && r.height > 1 && s.visibility !== 'hidden' && s.display !== 'none';
  });
  const inline = el => {           // exception: link inside a sentence of text
    if (el.tagName !== 'A') return false;
    const p = el.parentElement; if (!p) return false;
    const txt = (p.innerText || '').replace((el.innerText || ''), '').trim();
    return getComputedStyle(el).display === 'inline' && txt.length > 20;
  };
  const rects = els.map(el => el.getBoundingClientRect());
  const fails = [];
  els.forEach((el, i) => {
    const r = rects[i];
    if (r.width >= 24 && r.height >= 24) return;
    if (inline(el)) return;
    const cx = r.left + r.width / 2, cy = r.top + r.height / 2;
    // spacing exception: 24px circle (radius 12) must not intersect another target's rect
    const crowded = rects.some((o, j) => {
      if (j === i) return false;
      const nx = Math.max(o.left, Math.min(cx, o.right)), ny = Math.max(o.top, Math.min(cy, o.bottom));
      return Math.hypot(cx - nx, cy - ny) < 12;
    });
    if (crowded) fails.push({tag: el.tagName.toLowerCase(), w: Math.round(r.width), h: Math.round(r.height),
                             label: (el.getAttribute('aria-label') || el.innerText || el.getAttribute('title') || '').trim().slice(0, 40)});
  });
  return {checked: els.length, fails};
}"""

OBSCURED_JS = r"""() => {
  const el = document.activeElement;
  if (!el || el === document.body) return null;
  const r = el.getBoundingClientRect();
  if (r.width === 0 || r.height === 0) return null;
  const pts = [[r.left + 2, r.top + 2], [r.right - 2, r.top + 2], [r.left + 2, r.bottom - 2],
               [r.right - 2, r.bottom - 2], [r.left + r.width / 2, r.top + r.height / 2]]
    .filter(([x, y]) => x >= 0 && y >= 0 && x < innerWidth && y < innerHeight);
  if (!pts.length) return null;                     // off-screen: browser will scroll it
  const covered = pts.every(([x, y]) => { const hit = document.elementFromPoint(x, y);
                                          return hit && hit !== el && !el.contains(hit) && !hit.contains(el); });
  if (!covered) return null;
  const cover = document.elementFromPoint(pts[0][0], pts[0][1]);
  const fixed = cover && cover.closest('*') && [...function* (n) { while (n) { yield n; n = n.parentElement; } }(cover)]
      .find(n => ['fixed', 'sticky'].includes(getComputedStyle(n).position));
  return {focused: (el.getAttribute('aria-label') || el.innerText || el.tagName).trim().slice(0, 40),
          coveredBy: fixed ? (fixed.id ? '#' + fixed.id : [fixed.tagName.toLowerCase(), ...[...fixed.classList].slice(0, 2)].join('.')) : 'unknown'};
}"""


async def main(url, tabs):
    async with async_playwright() as pw:
        browser = await pw.chromium.launch()
        page = await browser.new_page(viewport={"width": 1366, "height": 768})
        await page.goto(url, wait_until="load")
        await page.wait_for_timeout(2000)
        t = await page.evaluate(TARGETS_JS)
        print(f"SC 2.5.8 Target Size: {len(t['fails'])} failing of {t['checked']} visible targets")
        for f in t["fails"][:25]:
            print(f"  {f['tag']:6} {f['w']}x{f['h']}px  \"{f['label']}\"")
        hidden = []
        await page.evaluate("document.activeElement && document.activeElement.blur()")
        # Forward, then backward: Shift+Tab scrolls elements in at the TOP edge,
        # which is exactly where sticky headers sit.
        for key in ["Tab"] * tabs + ["Shift+Tab"] * tabs:
            await page.keyboard.press(key)
            await page.wait_for_timeout(120)
            try:
                hit = await page.evaluate(OBSCURED_JS)
            except Exception:          # focus moved into something that navigated
                break
            if hit and hit not in hidden:
                hidden.append(hit)
        print(f"\nSC 2.4.11 Focus Not Obscured: {len(hidden)} element(s) fully hidden while focused ({tabs} Tab + {tabs} Shift+Tab)")
        for h in hidden:
            print(f"  \"{h['focused']}\" hidden by {h['coveredBy']}")
        await browser.close()


if __name__ == "__main__":
    ap = argparse.ArgumentParser()
    ap.add_argument("url")
    ap.add_argument("--tabs", type=int, default=60)
    a = ap.parse_args()
    asyncio.run(main(a.url, a.tabs))

Output on a test page with a fixed 120px header and two 16px icon links:

text
SC 2.5.8 Target Size: 2 failing of 5 visible targets
  a      16x16px  ""
  a      16x16px  ""

SC 2.4.11 Focus Not Obscured: 1 element(s) fully hidden while focused (8 Tab + 8 Shift+Tab)
  "Link one" hidden by header

The inline link in the first paragraph was correctly exempt, and only the Shift+Tab pass exposed the hidden link.


How BugViso Checks WCAG 2.2 Automatically

BugViso runs a self-hosted axe-core engine on every audited page with the wcag2a, wcag2aa, wcag21a, wcag21aa and wcag22aa rule tags. 2.2's Target Size (Minimum) is therefore tested alongside the full 2.1 A/AA rule set, in a real Chromium render after scripts and lazy content have loaded.

The mobile device-emulation pass measures tap targets against the WCAG 2.5.8 24Γ—24 px minimum and reports smaller 44px recommended targets separately as guidance, not failures. It also flags horizontal overflow and viewport problems. Each violation in the report names the rule, the WCAG criterion, the impact level and the failing elements, and the same data goes into the PDF report.

BugViso doesn't replace manual testing for the criteria marked Manual above, such as consistent help, redundant entry and authentication flows. Run a free BugViso accessibility scan to cover the automatable part in a minute.


High-Risk Oversights

  • Testing only with Tab. Focus Not Obscured failures usually appear with Shift+Tab, and with cookie banners still open.
  • Assuming 24px means "make everything 24px". A 16px icon with enough clear space passes under the spacing exception.
  • Blocking paste on login or "confirm email" fields. This breaks password managers and fails 3.3.8.
  • Treating overlays as 2.2 compliance. They don't change your default rendering, so they can't fix target size, focus obscuring or login flows.

FAQ

Is WCAG 2.2 legally required?

It depends on the jurisdiction. Many regulations and procurement standards still reference WCAG 2.1 AA. Because 2.2 is backwards compatible, conforming to 2.2 AA satisfies a 2.1 AA requirement too. Check the exact standard your law or contract cites, and get legal advice for compliance decisions.

How many success criteria are in WCAG 2.2 Level AA?

55: 31 at Level A and 24 at Level AA. That's the 50 A/AA criteria from WCAG 2.1, plus the six new A/AA criteria, minus the removed 4.1.1 Parsing.

Was anything removed in WCAG 2.2?

Yes. 4.1.1 Parsing is obsolete, because browsers now parse HTML consistently and malformed markup no longer breaks assistive technology by itself.

Can an automated tool make my site WCAG 2.2 compliant?

No. Automated tools find a meaningful share of failures, especially contrast, names, alt text and target size, but two-thirds of the 55 criteria need a human to evaluate. Use automation to clear the bulk, then test manually with keyboard and screen reader.


Conclusion

WCAG 2.2 adds six A/AA criteria, but real sites mostly still fail the old ones, so clear contrast, names and alt text first, then script the two new criteria you can test (target size and focus obscuring) and hand the rest to manual review, starting with a free BugViso WCAG scan.

Found this useful? Share it.

See where your site stands

Run a free BugViso audit for SEO, speed, accessibility and AI search readiness β€” with fixes you can ship today.