Compliance checklist

ADA Compliance Checklist for Web Developers (2026)

ADA website compliance means meeting WCAG 2.1 Level AA — the technical standard referenced in the DOJ's 2024 rules for both government and commercial websites. This checklist gives you 30 testable items organized by the four POUR principles: Perceivable, Operable, Understandable, and Robust. Each item includes a pass condition, a test method, and a note on what to fix when it fails.

Not legal advice. ShipSafe identifies common risk patterns based on WCAG guidelines. This checklist is educational and not a legal opinion. For a compliance determination that holds up in court, consult a qualified attorney.

A note on scope: this checklist covers Level A and Level AA success criteria — the two levels that together constitute WCAG 2.1 AA conformance. Level AAA criteria are aspirational; they are not a universal legal standard and are not included here. Two items from WCAG 2.2 are included and flagged with a 2.2 AA badge — they are worth implementing now even though US law hasn't formally adopted 2.2 yet.

1. Perceivable

Users must be able to perceive all content. Non-text content needs text alternatives; time-based media needs captions and transcripts; the visual presentation must meet contrast minimums.

  • 1.1.1 A A ⚠ often cited
    All informational images have descriptive alt text. Decorative images have alt="".

    Test: In DevTools, right-click any image → Inspect. Check for alt attribute. Run Axe or WAVE to catch missing alt attributes automatically. An image linked to another page must have alt text describing the link destination, not the image itself. Fix: Add meaningful alt text to informational images. Add alt="" to pure decorative images so screen readers skip them.

  • 1.2.2 A A
    Prerecorded video with audio has synchronized captions.

    Test: Play any embedded video and enable captions. Auto-generated YouTube captions do not satisfy this criterion — they must be reviewed and corrected. Fix: Upload a corrected .vtt or .srt file. For YouTube: Upload captions → Edit for accuracy → Publish. For HTML5 video: add a <track kind="captions"> element.

  • 1.2.3 A A
    Prerecorded audio-only content (podcasts, audio clips) has a full text transcript.

    Test: Look for a transcript link near any audio player on the page. Fix: Publish the transcript as accessible text on the same page or a linked page — not as a PDF, which is a separate accessibility problem.

  • 1.4.3 AA AA ⚠ often cited
    Text contrast is at least 4.5:1 for normal text; 3:1 for large text (≥ 18pt / ≥ 14pt bold).

    Test: Use the browser's colour-picker tool (DevTools → Elements → Colour) or the WebAIM Contrast Checker. Large text = 18pt (24px) or larger, OR 14pt bold (approximately 18.67px bold). Placeholder text counts. Fix: Darken text or lighten the background until the ratio passes. Decorative text and logo text are exempt.

  • 1.4.11 AA AA
    Non-text UI components (focus rings, icons, form borders) have contrast ≥ 3:1 against adjacent colors.

    Test: Check form field borders against the page background; check icon color against its background. A light-grey border on a white background almost always fails. Fix: Increase border or icon color contrast. Most design systems have a 3:1 minimum token available — swap to that token.

  • 1.4.1 A A
    Color is not the only means of conveying information (error states, required fields, status indicators).

    Test: View required form fields and error states in greyscale (Chrome DevTools → Rendering → Emulate vision deficiencies → Achromatopsia). If you can no longer tell which fields are required or which have errors, this criterion fails. Fix: Add a text label ("Required"), an icon (asterisk), or a border change — not just a color change.

  • 1.4.4 AA AA
    Text can be resized to 200% without loss of content or functionality. No horizontal scrollbar appears on the page at 200%.

    Test: Press Ctrl/Cmd + (or set browser zoom to 200%). Verify that all text is still readable, no text is clipped by fixed-height containers, and the page layout doesn't require horizontal scrolling. Fix: Replace fixed height on text containers with min-height. Avoid overflow:hidden on containers that hold text.

  • 1.4.5 AA AA
    Text is used to convey information rather than images of text (except logos and branding).

    Test: Look for images containing paragraph text, headings rendered as PNGs, or feature-list text inside images. Banner graphics with text copy are a common failure. Fix: Replace image-text with real HTML text styled with CSS. SVG text that meets contrast is acceptable. Logos and brand names are exempt.

2. Operable

Users must be able to operate all interface components and navigate the site using a keyboard, without time pressure or triggering seizures.

  • 2.1.1 A A
    All functionality is operable via keyboard alone — no mouse required for any action.

    Test: Unplug your mouse. Use Tab to move through the page, Enter/Space to activate links and buttons, and arrow keys for controls like menus and sliders. Every action available by mouse must be reachable by keyboard. Fix: Replace div and span click handlers with native <button> elements, or add tabindex="0" and keyboard event handlers to custom interactive elements.

  • 2.1.2 A A
    No keyboard trap: focus can always move away from any component using keyboard alone.

    Test: Tab into every interactive component — date pickers, custom dropdowns, embedded maps, and third-party widgets. Verify focus can leave each component with Tab, Shift+Tab, or Escape. Fix: Modals are the most common trap; they should close on Escape and return focus to the trigger element on close. Embedded iframes (maps, widgets) are frequent sources of traps.

  • 2.4.1 A A ⚠ often cited
    A skip navigation link ("Skip to main content") is the first focusable element on every page.

    Test: Press Tab once on any page — a "Skip to main content" link should appear. Pressing Enter should move focus directly past the navigation to the main content area. Fix: Add <a class="skip" href="#main">Skip to main content</a> as the first element in <body>, styled to be visible on focus but off-screen otherwise. Add id="main" to your <main> element.

  • 2.4.2 A A
    Every page has a unique, descriptive <title>. Not just the site name — "Contact | Acme Corp", not "Acme Corp".

    Test: Check the browser tab title on each page. SPAs are the most common failure — the <title> often stays as the homepage title across all routes. Fix: Update the document title on route changes using document.title in JavaScript or the appropriate router plugin for React/Vue/Svelte/Next.

  • 2.4.3 A A
    Focus order follows a logical reading sequence (top to bottom, left to right for LTR languages).

    Test: Tab through the entire page with keyboard only and observe where focus moves. Focus that jumps unpredictably (e.g., to a modal trigger before visible content, or into a footer before the nav) is a failure. Fix: Avoid positive tabindex values (tabindex="1", tabindex="2" etc.) — they override the natural DOM order and are the most common cause of disordered focus. Use DOM order to control tab order, not tabindex.

  • 2.4.7 AA AA ⚠ often cited
    A visible focus indicator is present on all interactive elements when they receive keyboard focus.

    Test: Tab through the page with keyboard. Every focused element — links, buttons, inputs, custom components — must show a clearly visible indicator. No outline: none or outline: 0 without a custom replacement. Fix: Remove :focus { outline: none } globally. Add a custom high-contrast focus ring using :focus-visible if you want to hide the default ring for mouse users while keeping it for keyboard users.

  • 2.3.1 A A
    No content flashes more than 3 times per second (to prevent seizure risk).

    Test: Look for animations or video with rapid flashing. The Photosensitive Epilepsy Analysis Tool (PEAT) can test recorded video. Fix: Remove rapid flash animations. Hero video autoplay content is the most common source — review any video content that includes strobe-like visual effects.

  • 2.2.1 A A
    If a time limit exists, users can turn it off, adjust it, or extend it before expiry.

    Test: Look for session timeouts, countdown timers on pricing pages, or auto-advancing slideshows. A 20-hour or more session timeout is exempt. Fix: Add a "Your session will expire in 2 minutes — extend?" modal with keyboard-accessible controls, or eliminate the timeout for non-security-critical interactions.

  • 2.5.8 AA 2.2 AA
    Interactive targets are at least 24×24 CSS pixels, or have sufficient offset spacing from adjacent targets.

    Test: In DevTools, inspect small icon buttons (hamburger menu, close X, social icons, pagination dots). Check computed height and width in the Layout panel. Anything under 24px in either dimension fails unless spaced with enough offset. Fix: Add padding to icon buttons to increase the clickable area without changing the visual icon size. A 16px icon with 8px padding on all sides becomes a 32×32px target.

3. Understandable

Users must be able to understand the content and how to operate the interface. Language must be declared, inputs must be labelled, and errors must be described in text.

  • 3.1.1 A A
    The <html> element has a lang attribute matching the page's primary language (e.g., lang="en").

    Test: View page source (Ctrl+U) and check the opening <html> tag. ShipSafe also flags this automatically. Fix: Add lang="en" (or the correct BCP 47 code) to the <html> element in your template. This is one of the fastest single-attribute fixes in the checklist.

  • 1.3.1 A A ⚠ often cited
    All form inputs have a programmatically associated label (<label>, aria-label, or aria-labelledby).

    Test: Click on any visible label text next to an input — if clicking the label moves focus to the input, the label is correctly associated. If nothing happens, the label is not programmatically associated. Placeholder text alone does not satisfy this criterion (it disappears when the user starts typing). Fix: Wrap input and label in a <label> element, or use <label for="inputId"> with a matching id on the input.

  • 3.3.1 A A
    Error messages identify the specific field that failed and describe the error in text.

    Test: Submit a form with intentional errors. The error message must name the field ("Email address is required") and describe what's wrong — not just highlight the field in red. Generic "Please correct the errors above" messages fail this criterion. Fix: Place error messages adjacent to the relevant input, not just at the top of the page. Use aria-describedby to link the error text to the input programmatically.

  • 3.3.2 A A
    Labels or instructions identify required fields and any input format requirements before submission.

    Test: Look for required fields — is there any visible indicator (not just a red asterisk with no explanation)? If a field requires a specific format (date: MM/DD/YYYY, phone: 10 digits), is that format stated near the input, not just in the error? Fix: Add "(required)" text or an asterisk with a legend explaining the asterisk convention. Add format hints below or within the label.

  • 2.4.6 AA AA
    Headings describe the topic or purpose of the section that follows them. Heading levels are not skipped (h1 → h2 → h3, not h1 → h3).

    Test: Install the Headings Map browser extension or run: document.querySelectorAll('h1,h2,h3,h4,h5,h6') in the console. Verify the hierarchy is logical and levels are not skipped. One h1 per page (the main page title). Fix: Reorder heading elements to follow document structure. Never use a heading level for visual size alone — use CSS classes instead.

  • 2.4.4 A A
    Link text is descriptive in isolation. Not "Click here", "Read more", or "Learn more" without context.

    Test: Screen readers can list all links on a page out of context. Every link text should make sense read alone. "Read the 2025 accessibility lawsuit statistics report" passes; "Read more" fails. Fix: Use descriptive link text in the visible label, or add visually-hidden text with a <span class="sr-only"> to supplement ambiguous link text. Adding aria-label on the link element also works.

  • 3.2.3 AA AA
    Navigation that appears on multiple pages is in the same relative position on each page.

    Test: Navigate through three to five different pages. Header, footer, and sidebar navigation should appear in a consistent position. Fix: Use a shared layout component or template partial so navigation is identical across pages. One-off pages with a different nav structure are the typical failure.

  • 3.2.2 A A
    Selecting an option or entering data does not cause an unexpected context change (auto-navigating on select, auto-submitting a form on change).

    Test: Use country or date picker dropdowns. Selecting an option should not immediately navigate to a new page or submit a form unless a submit button is explicitly present. Fix: Require explicit confirmation (a submit or "Go" button) for any select or radio element that causes navigation. Warn users in advance if a context change will happen.

4. Robust

Content must be correctly interpreted by assistive technologies. Semantically valid HTML and correctly applied ARIA let screen readers, Braille displays, and voice control tools work as intended.

  • 1.3.5 AA AA
    Input fields that collect personal data use the correct HTML autocomplete attribute so browsers and autofill tools can assist users.

    Test: Inspect name, email, phone, and address inputs. Verify they have autocomplete attributes: name, email, tel, street-address, etc. Missing autocomplete attributes force users with cognitive or motor disabilities to re-type information the browser could supply. Fix: Add the appropriate autocomplete token from the WCAG 1.3.5 purpose list to each personal-data input.

  • 4.1.2 A A
    All UI components have an accessible name, an accessible role, and expose their state where applicable.

    Test: Inspect custom controls (custom checkboxes, toggles, tabs, accordions) in DevTools → Accessibility tree. Each must have a name (label or aria-label), a role (checkbox, tab, button, etc.), and reflect state (aria-checked, aria-expanded, aria-selected). Fix: Use native HTML elements where possible — <button>, <input type="checkbox">, <details>. They come with role and state semantics built in. For custom components, add the appropriate ARIA role and keep state attributes updated in JavaScript.

  • 4.1.2 A A
    No ARIA roles or attributes are applied to elements where they are redundant or incorrect (e.g., role="button" on a <button>, or aria-hidden="true" on a focusable element).

    Test: Search the codebase for aria-hidden="true" — if any of those elements contain focusable children, screen readers will still find those children but won't be able to describe the context. Also search for redundant roles on native elements. Fix: Remove redundant ARIA from native elements. Never put aria-hidden="true" on a container that holds interactive elements.

  • 4.1.3 AA AA
    Status messages — form success, loading indicators, error summaries — are programmatically exposed without requiring the user to move focus.

    Test: Submit a form successfully. A sighted user sees a "Success!" message appear; does a screen reader user hear it announced? Unless focus is explicitly moved to the message, it must use role="status" or aria-live="polite". Fix: Add role="status" to success message containers and role="alert" to error summaries. The element must exist in the DOM before the message is injected into it — don't inject both the container and the message at once.

  • 1.3.1 A A
    The HTML structure conveys document semantics: landmarks (<header>, <main>, <nav>, <footer>) are used correctly, and tables use <th> with scope.

    Test: Check page source for landmark elements. Run Axe or the Accessibility Inspector in Firefox DevTools to see the landmark structure. Tables should have <th scope="col"> or <th scope="row"> elements identifying headers. Fix: Wrap navigation in <nav>, primary content in <main>, site header in <header>, and site footer in <footer>. If multiple nav elements exist, distinguish them with aria-label.

  • 2.4.11 AA 2.2 AA
    Focused elements are not entirely hidden behind sticky headers, cookie banners, or other fixed-position overlays.

    Test: Tab through the page while a sticky header and/or cookie consent banner is visible. Check that the focused element is at least partially visible — not completely underneath the fixed layer. Fix: Add scroll-margin-top on :focus equal to or slightly more than the sticky header height. For bottom banners, test form inputs near the page bottom.

Quick wins if you only have one hour

If you can't do a full audit right now, these five fixes address the criteria most commonly cited in ADA demand letters and automated-scan-driven complaints:

  1. Add lang="en" to the <html> element on every page — 30 seconds, one-line template fix.
  2. Add alt text to all informational images — run Axe to find the gaps, then add descriptive alt attributes.
  3. Associate labels with every form input — Axe catches unlabeled inputs; each takes 10–15 seconds to fix with a <label for>.
  4. Add a skip navigation link as the first element in <body>: <a class="skip" href="#main">Skip to main content</a>.
  5. Run ShipSafe on your live URL to catch contrast failures, missing alt text, missing labels, and missing language attributes automatically.

Check your site with ShipSafe

ShipSafe scans your page's HTML for the accessibility failures that appear most frequently in ADA demand letters — missing lang attribute, unlabeled form inputs, images without alt text, contrast failures, missing skip navigation, and no privacy policy link. Use it after making changes to confirm your fixes are in the markup, not just visually apparent.

Check my site with ShipSafe →

More ShipSafe guides:

Frequently asked questions

Which criteria appear most often in ADA demand letters?

The four most common findings in automated-scan-driven ADA demand letters are: missing or empty alt text on informational images; form inputs without associated labels; text failing the 4.5:1 color contrast ratio; and no skip navigation link. These four items account for the majority of complaints generated by plaintiff firms using automated accessibility scanners. Fixing them first reduces legal exposure the most per hour of work.

Does this checklist cover WCAG 2.2 or only WCAG 2.1?

The checklist is based on WCAG 2.1 Level AA, which is what current US law (the DOJ's 2024 rules under ADA Title II and Title III) references. Two WCAG 2.2 AA items are included and flagged with a 2.2 AA badge: Target Size Minimum (2.5.8) and Focus Not Obscured (2.4.11). These are worth implementing now even though US law hasn't formally adopted 2.2 yet — the gap from 2.1 is small, the fixes are typically minor CSS changes, and the standard will migrate. See the WCAG 2.1 vs 2.2 guide for the full list of changes.

Do I need a professional accessibility audit, or can I self-audit with this checklist?

This checklist catches the most common failures — the ones that appear most frequently in litigation and automated audits. It is not a substitute for a formal WCAG conformance evaluation, which requires manual testing with actual screen readers across multiple browsers and platforms. For a legally defensible compliance record, a professional audit by a qualified accessibility specialist provides stronger protection. Working through this checklist first will resolve the majority of issues and put you in a much better position before engaging an expert.

What automated tools complement this checklist?

Axe DevTools (free browser extension from Deque), WAVE (WebAIM's free online checker), and ShipSafe catch approximately 30–40% of WCAG failures automatically — reliably finding contrast failures, missing alt text, unlabeled inputs, and language attribute omissions. The remainder require manual keyboard navigation testing and screen reader testing. For screen readers: NVDA is free for Windows; VoiceOver is built into macOS and iOS; TalkBack is built into Android. Test with keyboard alone first — that surfaces the most issues the fastest.

How long does a full audit against this checklist take?

An experienced developer using automated tools (Axe/WAVE/ShipSafe) plus manual keyboard navigation can typically complete the 30-item checklist on a simple marketing or SaaS site in 2–4 hours. Complex applications with dynamic content, custom interactive components, and rich interactions take longer. Running an automated scan first eliminates all items that pass automatically, focusing manual effort on what can't be machine-checked. Allow 30–60 minutes for screen reader testing on top of the automated pass.

Does fixing accessibility require screen reader testing?

Not for most of the items on this checklist. Alt text, contrast, labels, skip nav, heading structure, and language attributes are all verifiable with browser DevTools and Axe/WAVE. Screen reader testing — with NVDA, VoiceOver, or TalkBack — is essential for verifying the Robust section items: that ARIA is applied correctly, that dynamic content changes are announced, and that custom interactive components (modals, accordions, carousels, custom menus) work correctly for blind users. Plan for at least one round of screen reader testing before considering a site WCAG 2.1 AA compliant.

Also in the Copper Bay Labs suite

Accessibility is one layer of pre-launch risk. These free tools cover the rest: