Web Accessibility · 2026 Edition

Interactive accessibility training
for web developers.

Learn WCAG 2.1 & 2.2, ADA, and Section 508 through interactive labs, screen reader simulations, keyboard testing, and production-ready UI patterns.

WCAG criteria
87
UI patterns
10
Playgrounds
4
Live tools
5

Interactive playgrounds

Feel the friction.

Reading WCAG won't make you fluent. Each playground below is a real, working comparison — toggle, tap, and try the broken version to understand why the criterion exists.

WCAG 2.2 · 2.5.8 Level AA

Target Size (Minimum)

Interactive targets must be at least 24 × 24 CSS pixels. Below the threshold, users with motor disabilities, tremors, or touchscreens systematically miss.

Fails 2.5.8

A 16 × 16 px button. Hard to hit. Watch your error rate.

Hits: 0
Passes 2.5.8

A 44 × 44 px button. Comfortable, conformant, and beyond AAA.

Hits: 0

Pro tip: Apple HIG recommends 44 × 44 pt, Material recommends 48 × 48 dp. The WCAG 2.2 floor of 24 × 24 px is the legal minimum, not the target.

WCAG 2.2 · 3.3.7 Level A

Redundant Entry

Information previously entered in the same process must be auto-populated or selectable — never re-typed. Helps users with cognitive disabilities and short-term memory loss.

Fails 3.3.7

Shipping address

Billing address (re-enter)

⚠️ User must re-type 3 fields they just entered.

Passes 3.3.7

Shipping address

Billing address

✓ One checkbox eliminates re-entry entirely.

WCAG 2.1 · 1.4.11 Level AA

Non-text Contrast

UI components and meaningful graphics need a contrast ratio of at least 3:1 against adjacent colors. Borders, icons, states — not just text.

Create your account

Quick sign-up to continue.

Border ratio
1.2:1
Button ratio
1.4:1
Icon ratio
1.3:1
WCAG 2.1 · 1.3.5 Level AA

Identify Input Purpose

Use HTML autocomplete tokens so browsers, password managers, and assistive tech understand each field's purpose.

Fails 1.3.5

No autocomplete. The browser can't help.

Not accessible signin.html
<input type="text"
       placeholder="first thing">

No autocomplete token. Browser autofill, password managers, and assistive tech have no idea what this field is for.

Passes 1.3.5

Semantic autocomplete tokens. Try focusing each field — your browser will offer saved data.

Accessible · WCAG 1.3.5 signin.html
<input type="text" name="name"
       autocomplete="name">

The highlighted autocomplete="name" token tells browsers, password managers, and screen readers exactly what this field collects.

Common autocomplete tokens reference
nameFull name
given-nameFirst
family-nameLast
emailEmail
telPhone
street-addressAddress
postal-codeZIP / postcode
cc-numberCard number
bdayBirthday
current-passwordExisting pw
new-passwordNew pw
one-time-codeSMS/2FA code

Pattern library

Components, done right.

Production-ready patterns with the keyboard map, the accessible name, and the code in your framework. Copy, adapt, ship.

Modal Dialog

2.1.2 2.4.3 4.1.2
EscClose TabTrapped EnterActivate
<dialog aria-labelledby="d-h">
  <h2 id="d-h">Confirm action</h2>
  <button autofocus>Cancel</button>
  <button>Confirm</button>
</dialog>
// Native <dialog> gives focus trap + Esc free
dialog.showModal();
<dialog ref={dialogRef} aria-labelledby="d-h">
  <h2 id="d-h">Confirm action</h2>
  <button autoFocus onClick={close}>Cancel</button>
</dialog>
// dialogRef.current.showModal()
<dialog ref="dialog" aria-labelledby="d-h">
  <h2 id="d-h">Confirm action</h2>
  <button @click="close" autofocus>Cancel</button>
</dialog>
// this.$refs.dialog.showModal()

Tabs

2.1.1 4.1.2
Project status, recent updates, team members.
← →Switch tab Home/EndFirst/Last
<div role="tablist">
  <button role="tab" aria-selected="true"
          aria-controls="p1" id="t1">Overview</button>
</div>
<div role="tabpanel" id="p1" aria-labelledby="t1">...</div>
// Arrow keys cycle, only selected tab is in tab order
<div role="tablist">
  {tabs.map((tab, i) => (
    <button key={i} role="tab"
       aria-selected={active === i}
       tabIndex={active === i ? 0 : -1}>
       {tab.label}
    </button>
  ))}
</div>

Combobox (Autocomplete)

2.1.1 4.1.2 1.3.1
    ↑ ↓Navigate EnterSelect EscClose
    <input role="combobox"
           aria-autocomplete="list"
           aria-expanded="false"
           aria-controls="list"
           aria-activedescendant="opt-3">
    <ul id="list" role="listbox">
      <li id="opt-3" role="option"
          aria-selected="true">Brazil</li>
    </ul>

    Toast / Snackbar

    4.1.3 2.2.1
    Changes saved successfully
    aria-live="polite"Announced softly
    <div role="status"
         aria-live="polite"
         aria-atomic="true">
      Changes saved successfully
    </div>
    // "polite" waits for the user to finish
    // Use "assertive" only for errors

    Tooltip

    1.4.13 4.1.2
    This is dismissible & persistent
    aria-describedbyRead after name EscDismissible
    <button aria-describedby="tip">Help</button>
    <div id="tip" role="tooltip">
      Click to share this article
    </div>
    // WCAG 1.4.13: must be hoverable + persistent + dismissible

    Skip Link

    2.4.1

    This page has a real one. Try pressing Tab from the address bar.

    It's the first thing screen reader & keyboard users hit. It must be visible on focus.

    TabFrom page top reveals it
    <a href="#main" class="skip">
      Skip to main content
    </a>
    
    /* Off-screen until focused */
    .skip { position: absolute; top: -100px; }
    .skip:focus { top: 1rem; }

    Accordion / Disclosure

    2.1.1 4.1.2
    Space / EnterToggle aria-expandedRequired
    <button aria-expanded="false"
            aria-controls="panel">
      Shipping info
    </button>
    <div id="panel" role="region"
         hidden>
      Free shipping…
    </div>
    // Toggle: button[aria-expanded] + panel[hidden]
    // Native: <details><summary> is simpler if pure HTML

    Menu / Dropdown

    2.1.1 4.1.2
    ↓ / EnterOpen ↑ ↓Navigate EscClose
    <button aria-haspopup="menu"
            aria-expanded="false"
            aria-controls="m">Actions</button>
    <ul id="m" role="menu">
      <li role="menuitem" tabindex="-1">Duplicate</li>
    </ul>
    // Note: a dropdown of LINKS is just a <ul> of <a> — no role="menu" needed

    Form Validation

    3.3.1 3.3.3 4.1.3
    aria-invalidMark errors aria-describedbyLink error text role="status"Announce
    <label for="e">Email</label>
    <input id="e" type="email" required
           aria-invalid="true"
           aria-describedby="e-err">
    <p id="e-err">Please enter a valid email.</p>
    <div role="status" aria-live="polite">
      Form has 2 errors. <a href="#e">Fix email</a>
    </div>
    // Validate on blur, not on every keystroke

    Date input

    1.3.5 3.3.2 4.1.2

    MM/DD/YYYY

    type="date"Native picker autocompleteWCAG 1.3.5
    <label for="d">Departure date</label>
    <input id="d" type="date"
           autocomplete="bday"
           aria-describedby="d-fmt">
    <p id="d-fmt">MM/DD/YYYY</p>
    // Native is almost always better than a custom calendar
    // Custom calendars: APG datepicker is the reference

    Live tools

    Test as you design.

    Stop guessing. Pick colors, see contrast. Click any element, see its accessibility tree. No tab switching.

    Contrast checker

    Aa

    The quick brown fox jumps over the lazy dog.

    Ratio
    21:1
    Higher is better
    Compliance

    ARIA tree inspector

    Click any element on this page to see its accessibility tree representation — what a screen reader actually receives.

    Click any element to inspect it…

    Health check

    Paste any HTML — a component, a page, a snippet — and get a fast static analysis. Catches the obvious 80% (missing alt text, unlabeled inputs, contrast issues, heading order, missing landmarks). Not a substitute for a real audit, but it'll surface what an auditor would flag in the first 5 minutes.

    Headings outline

    The H1–H6 tree of this page, the way a screen reader navigates it. Skipped levels, missing H1, and empty headings are flagged. Click any heading to scroll to it.

    Landmarks viewer

    Toggle the overlay to see every landmark on this page — banner, navigation, main, complementary, contentinfo, search, region, form — labeled and outlined. This is the "page outline" screen reader users perceive when they jump by region.

    Daily reference

    The reference manual.

    The four accessibility references web developers reach for most: ARIA attributes and roles, keyboard interactions by widget, semantic HTML, and live regions.

    ARIA reference

    Every ARIA attribute and role you'll reach for, with what it does and when not to use it. The first rule of ARIA: don't use ARIA if HTML can do it.

    Keyboard interaction by widget

    The keys each widget needs to support, distilled from the WAI-ARIA Authoring Practices. If you're building a custom component, this is the contract.

    Semantic HTML

    The foundation. Get this right and you skip half the ARIA you'd otherwise need.

    Landmarks

    Landmarks are how screen reader users jump around a page. Use the HTML elements — they have implicit ARIA roles and need no extra markup.

    ElementImplicit roleUse for
    <header>banner *Top of the page: logo, primary nav, search. * Only when a direct child of <body> — otherwise just a section header.
    <nav>navigationMajor navigation blocks. Multiple allowed — label each: aria-label="Primary", "Footer", etc.
    <main>mainPrimary content. One per page. Skip links target this.
    <aside>complementarySidebar, callout, related content — tangential to main flow.
    <footer>contentinfo *Site-wide footer: copyright, legal, secondary nav. * Only as direct child of <body>.
    <section>region (only with name)A generic section. Becomes a landmark only when given an accessible name via aria-labelledby or aria-label.
    <search>searchSearch interface (HTML 2023). Replaces role="search" on <form>.
    <form>form (only with name)Becomes a landmark only when given an accessible name.

    Heading hierarchy

    • One <h1> per page describing the page's primary purpose.
    • No skipping levels. Don't go h1 → h3. Screen readers navigate the hierarchy and gaps confuse the structure.
    • Use heading levels for semantics, not size. If the visual design needs a small heading, use <h2 class="text-sm"> — not <h4>.
    • Don't use empty headings. If you need spacing, use CSS. Empty <h2></h2> is announced as "heading level 2, blank" — disorienting.
    • For SPA route changes that don't reload the page: move focus to the new <h1> (with tabindex="-1") so screen reader users know they've navigated.

    Button vs link vs div

    ElementWhenGets you
    <button>Triggers an action on the current page (open modal, submit form, toggle state).Keyboard activation, Space+Enter, focus, role="button", correct AT announcement.
    <a href>Navigates somewhere — different page, anchor, external URL.Enter activates (Space scrolls!), right-click context menu, "open in new tab".
    <div role="button">Almost never. Only if there's an extraordinary reason a real <button> won't work.Nothing automatic. You add tabindex="0", Space/Enter handlers, disabled state — all the things <button> gives free.

    Test: if "open in new tab" makes sense, it's a link. Otherwise it's a button.

    Modern interactive elements

    Element / attributeWhat it does
    <details> <summary>Native disclosure widget. Keyboard, ARIA, animation — all free. Use this before reaching for custom JS.
    <dialog>Native modal element. showModal() handles focus trap, Esc-to-close, inert background automatically. Use this before building a custom modal.
    popover attributeNative popover (2024). For tooltips, dropdowns, and non-modal overlays. Pairs with popovertarget on the trigger.
    inert attributeRemoves a subtree from the accessibility tree and keyboard focus. Use on backgrounds while a modal is open.
    <input list>Native combobox via <datalist>. Limited but adequate for simple cases.

    The four ways to hide

    MethodVisualScreen readerKeyboardUse for
    display: noneHiddenHiddenSkippedTabs (inactive panel), accordion (closed panel).
    visibility: hiddenHiddenHiddenSkippedSame as display:none, but reserves layout space.
    aria-hidden="true"VisibleHiddenFocusable still!Decorative icons next to text. Never on focusable elements.
    .sr-only (CSS)HiddenReadSkipped (usually)Labels for icon buttons, status text, skip links until focused.

    Live regions

    How to announce things without moving focus. Among the most-misused parts of ARIA.

    Decision matrix

    What you're announcingUse thisWhy
    Form saved, item added to cart, copied to clipboardrole="status" or aria-live="polite"Non-urgent. Waits for the SR to finish its current sentence.
    Form has errors, payment failed, session expiringrole="alert" or aria-live="assertive"Urgent. Interrupts whatever the SR is reading.
    Chat message arrived, log entry appendedrole="log"Sequential additions. SR knows order matters.
    Elapsed time, countdownrole="timer"SR can choose not to announce every tick.
    Modal confirming a destructive actionrole="alertdialog"Combines alert urgency with dialog focus semantics.

    The supporting attributes

    AttributeDefaultWhat it controls
    aria-atomicfalseWhen true: the SR reads the entire region on any change. When false: just the changed part. Default is fine for most cases.
    aria-relevantadditions textWhich change types trigger an announcement: additions, removals, text, all. Don't override unless you have a specific reason.
    aria-busyfalseSet true while updating a region to suppress announcements until done. Set back to false to flush.

    Gotchas that will bite you

    • Live regions don't announce on initial render. The element must already exist in the DOM before the content changes. Render the empty region first, then update it.
    • Same content twice in a row may not re-announce. Screen readers deduplicate. To force re-announcement, briefly clear the region then set it again (or use a counter/timestamp in the text).
    • Moving focus interrupts announcements. If you announce and focus a new element, the focus change wins. For toast-style messages: don't move focus.
    • Assertive is for emergencies. Don't use it for "added to cart." Real users mute pages that constantly interrupt.
    • Visually hidden live regions still work. Use .sr-only if the announcement should be SR-only and not on screen.
    • Test in multiple SRs. NVDA, JAWS, VoiceOver, TalkBack all handle live regions slightly differently. The behavior you tested in one isn't the behavior in another.

    Code review

    Pre-merge checklist.

    The questions to ask before approving any PR with UI changes. Keep this open during code review. Progress saves locally.

    Federal Law

    Section 508

    A 1998 amendment to the Rehabilitation Act of 1973. Requires federal agencies to make their electronic and information technology (EIT) accessible to people with disabilities.

    Who it covers
    Federal agencies, their contractors, and any organization that develops, procures, or maintains technology used by the federal government.
    The standard
    The 2018 "ICT Refresh" formally adopted WCAG 2.0 Level A & AA. Many agencies are now voluntarily targeting WCAG 2.1 AA pending the next refresh.
    Enforcement
    Administrative complaints filed with the agency; civil suits permitted under §508(f). Federal employees may sue for damages and equitable relief.
    Procurement teeth
    Vendors must produce an ACR/VPAT (Accessibility Conformance Report / Voluntary Product Accessibility Template) to bid on federal contracts.
    Civil Rights Law

    ADA Title III

    The Americans with Disabilities Act (1990). Title III prohibits discrimination by private places of public accommodation. Courts increasingly include websites and apps in this definition.

    Who it covers
    Private businesses open to the public: retailers, restaurants, hotels, banks, healthcare, education, entertainment, and increasingly, all consumer-facing digital services.
    The standard
    No statutorily defined technical standard for the web (yet). The DOJ has repeatedly affirmed that WCAG 2.1 AA is the de facto compliance benchmark in litigation and settlements.
    Enforcement
    Private right of action — any individual can sue. The DOJ also enforces. Title III itself allows injunctive relief plus attorney's fees; many states (e.g. California's Unruh Act) add statutory damages of $4,000+ per visit.
    Landmark case
    Robles v. Domino's Pizza (9th Cir., 2019; cert. denied 2019) — confirmed Title III applies to websites and apps with a nexus to a physical place of accommodation.

    Real cases. Real costs.

    When accessibility isn't built in, courts decide what it costs. Selected landmark cases:

    Robles v. Domino's Pizza

    9th Circuit Court of Appeals · 2019
    Outcome

    Cert. denied by Supreme Court — confirmed ADA Title III applies to websites and apps with a nexus to a physical place of accommodation.

    Blind plaintiff couldn't order pizza via Domino's website or mobile app with a screen reader.

    Missing alt text, unlabeled form fields, custom JS controls without ARIA — the basics.

    NFB v. Target

    N.D. Cal. · 2008 (settlement)
    $6,000,000 Settlement

    First major ruling that retail e-commerce sites are subject to the ADA. Target paid $6M + adopted WCAG-equivalent standards.

    Inaccessible image maps, no keyboard navigation, missing alt text on product photos.

    NY AG v. Sephora

    New York · 2022 (settlement)
    $2,000,000 Penalty + remediation

    Sephora.com inaccessible to blind users; required to bring site to WCAG 2.1 AA and audit annually.

    Color-only error states, missing form labels, focus traps in product carousel.

    Andrews v. Blick Art Materials

    E.D.N.Y. · 2017
    Outcome

    Federal judge held the ADA applies to standalone commercial websites — no physical nexus required.

    Used in subsequent suits as precedent for "pure" e-commerce. Opened the door to thousands of filings.

    The trend: ADA digital accessibility lawsuit filings surpassed 4,500 in 2023 — up from under 100 in 2014. The plaintiff's bar has industrialized this. Average defense cost: $250K–$1M even if you win.

    At a glance

    Dimension Section 508 ADA Title III
    Scope Federal government & contractorsPrivate public accommodations
    Adopted standardWCAG 2.0 AA (ICT Refresh)WCAG 2.1 AA (de facto)
    Enforcement Agency complaints, §508(f) suitsPrivate suits + DOJ
    Penalties Contract loss, remediation ordersInjunctions, fees, state damages
    Documentation VPAT / ACR requiredAudit report recommended

    Quick reference

    What's new in WCAG 2.2.

    Published October 2023. Nine new success criteria, all focused on cognitive disabilities, motor accessibility, and authentication friction.

    2.4.11
    Focus Not Obscured (Minimum)
    Level AA · The focused element must not be entirely hidden by other content.
    Sticky headers and cookie banners are the usual culprits. When a user tabs to an element, at least part of the focused element must remain visible. The strict AAA version (2.4.12) requires fully visible.
    2.4.13
    Focus Appearance
    Level AAA · Focus indicators must be at least 2 px thick with a 3:1 contrast ratio.
    The default browser focus ring rarely passes. This dashboard uses a 4 px outline with high contrast — try tabbing through it to see.
    2.5.7
    Dragging Movements
    Level AA · Any drag operation must have a single-pointer alternative.
    Kanban boards, signature pads, range sliders — provide buttons or text input as alternatives.
    3.3.8
    Accessible Authentication (Minimum)
    Level AA · No cognitive function test without an alternative.
    Don't force users to memorize, transcribe, or solve puzzles. Allow paste, support password managers, and offer alternatives like passkeys, magic links, or biometrics.

    Mobile a11y

    Mobile is different.

    Touch targets, orientation, reflow, motion. The criteria desktop audits skip get audited harder on mobile.

    2.5.5 / 2.5.8

    Touch target size

    iOS HIG: 44pt. Material: 48dp. WCAG 2.2 AA floor: 24×24 CSS px. Always design to the platform max, not the WCAG floor.

    1.3.4

    Orientation

    Don't lock to portrait or landscape. Some users mount their device permanently in one orientation.

    1.4.10

    Reflow

    Content reflows to 320px CSS px wide without two-axis scrolling. Test in 320×256.

    1.4.12

    Text spacing

    Layout must survive 200% line height, 50% paragraph space, 16% letter space — without clipping or overlap.

    2.5.4

    Motion actuation

    If shake-to-undo exists, also offer a button. Motion-only triggers exclude users with tremors or mounted devices.

    1.4.4

    Text scaling

    Respect iOS Dynamic Type and Android font scale. Use scalable units, not absolute px for body text.

    2.5.1

    Pointer gestures

    Anything done with a swipe/pinch must also work with a single tap (or button). Don't gate features behind gestures.

    Spacing

    Target spacing

    Adjacent touch targets need 8px+ gap to prevent mis-taps for users with motor impairments. WCAG 2.5.8 partly covers this via "spacing".

    All 87 criteria

    The complete WCAG index.

    Every success criterion across WCAG 2.0, 2.1, and 2.2 — searchable, filterable, in plain English. Tap any number to jump to the W3C definition.

    Level
    Version
    Principle

    Test yourself

    Quick check.

    Six questions. Real scenarios. Instant feedback with the WCAG citation that proves the answer.