Skills MCP Learn Benchmarks Tools News
Learn · Guides · Quality

Accessibility for AI-Built Sites.

Target WCAG 2.2 Level AA on every AI-generated UI — prompt contracts, semantic HTML, axe-core in CI, and a checklist you can run before ship.

SPONSOR

AppSignal — Stop vibe-debugging. Every exception, every backtrace, grouped so you see patterns, not noise.

↗
On this page
  1. What agents get wrong
  2. The a11y prompt contract
  3. Keyboard, focus, and ARIA
  4. Forms and errors
  5. Testing with axe-core
  6. The CI gate
  7. Live: WCAG 2.2 AA checklist
  8. Common pitfalls
CH 01

What agents get wrong.

AI agents can recite WCAG success criteria from memory and still ship interfaces that fail a five-minute keyboard test. The gap is not knowledge. It is incentives. Accessibility costs tokens, and nothing in a default prompt rewards it.

The same mistakes show up across Cursor, Claude Code, Codex, and v0:

  • Div soup with ARIA patches. A <div role="button" tabindex="0"> instead of a <button>. It looks fine, breaks Enter/Space handling, and confuses every screen reader.
  • Placeholder-as-label. Inputs with placeholder="Email" and no <label>. Placeholders disappear on focus and are not announced reliably.
  • Icon-only controls with no accessible name. A trash-can SVG with no text, no aria-label, and no visually hidden span. VoiceOver reads "button".
  • Custom modals without focus management. Overlays that trap scroll but not Tab. Focus escapes to the page behind. Escape key does nothing.
  • Contrast that fails in dark mode. Agents love text-gray-400 on bg-gray-900. That is roughly 2.8:1. WCAG AA requires 4.5:1 for normal text.
  • Tap targets under 24px. WCAG 2.2 added 2.5.8 Target Size (Minimum): interactive targets need at least 24×24 CSS pixels, or equivalent spacing. Agents shrink icon buttons to 16px because it looks cleaner.
  • Sticky headers covering focused elements. 2.4.11 Focus Not Obscured (Minimum) means focused controls cannot be fully hidden by fixed nav bars, cookie banners, or chat widgets.

The baseline is WCAG 2.2 Level AA, not 2.1. Same core requirements, plus criteria that matter on modern SPAs: focus visibility under overlays, minimum target size, accessible authentication flows, and consistent help placement (3.2.6 Consistent Help).

Contrast ratios to memorize: 4.5:1 for normal text, 3:1 for large text (18pt+ or 14pt bold+), and 3:1 for non-text UI components like icons, borders, and focus indicators.

Install the frontend-accessibility skill in your agent so these rules persist across sessions. Pair it with Guide 13 for UI prompt patterns and Guide 09 for the broader test strategy.

Pitfall · "It passes Lighthouse"

Lighthouse accessibility score is a rough heuristic, not a WCAG audit. A page can score 95 and still fail keyboard navigation, meaningful alt text, and form error association. Use axe-core for automated checks and your keyboard for the rest.

CH 02

The a11y prompt contract.

One paragraph in AGENTS.md beats ten reminders per session. Paste this block at the repo root and point every UI task at it.

AGENTS.md · accessibility contract
## Accessibility (WCAG 2.2 Level AA, mandatory)

- Semantic HTML first: <button>, <a>, <label>, <dialog>, <details>
- No div/span with role="button" unless native element is impossible
- Every input has a visible <label> or aria-labelledby
- Color contrast: 4.5:1 normal text, 3:1 large text, 3:1 UI components
- Focus visible; focused element not fully obscured (2.4.11)
- Interactive targets ≥ 24×24 CSS px or equivalent spacing (2.5.8)
- Modals: native <dialog> with showModal(), or Popover API
- Auth flows: no cognitive function test only (3.3.8)
- Help links in consistent location across pages (3.2.6)
- Run axe-core before marking UI tasks done; zero critical/serious violations

For individual tasks, prepend a one-liner so the agent cannot skip it:

Per-task prompt prefix
Build the checkout form component. WCAG 2.2 AA required:
native labels, keyboard-operable, 4.5:1 contrast, 24px targets,
errors linked with aria-describedby, must pass axe-core with zero
critical violations. Use <button> not div buttons.

See Guide 13 for the full component-first prompt shape. Accessibility belongs in the same spec block as states, tokens, and types, not in a follow-up "now add a11y" message.

CH 03

Keyboard, focus, and ARIA.

The fix for most agent output is deletion, not addition. Remove the custom widget. Use the platform primitive. Native elements carry keyboard behavior, focus semantics, and screen reader mappings for free.

Prefer semantic HTML over ARIA. The first rule of ARIA: do not use ARIA if a native element exists. Agents reach for role="tablist" when <details> or anchor links would work.

Native <dialog> (focus trap included)
<button id="open-settings">Settings</button>

<dialog id="settings-dialog" aria-labelledby="settings-title">
  <h2 id="settings-title">Settings</h2>
  <!-- form content -->
  <form method="dialog">
    <button value="cancel">Cancel</button>
    <button value="save">Save</button>
  </form>
</dialog>

<script>
const dlg = document.getElementById('settings-dialog');
document.getElementById('open-settings').addEventListener('click', () => {
  dlg.showModal(); // traps focus, Escape closes, inert backdrop
});
</script>

Popover API for lightweight overlays (menus, tooltips that behave like popovers). The browser handles focus and popover light-dismiss. Agents rarely know this API exists. Teach it explicitly.

Popover API pattern
<button popovertarget="user-menu" aria-expanded="false">
  Account
</button>
<div id="user-menu" popover>
  <a href="/profile">Profile</a>
  <a href="/logout">Log out</a>
</div>

Focus rules that agents violate constantly:

  • Never use tabindex values above 0. They hijack tab order.
  • Visible focus styles on every interactive element. outline: none without a replacement is a WCAG failure.
  • Under fixed headers, scroll focused elements into view or add scroll-padding so 2.4.11 passes.
  • Return focus to the trigger when a dialog closes.

The frontend-accessibility skill has roving tabindex patterns for tabs and menus when native elements are not enough.

CH 04

Forms and errors.

Agents generate pretty forms and forget the failure path. WCAG cares more about what happens when validation fails than about border-radius.

Non-negotiables for every form an agent touches:

  • Visible labels tied with for/id or aria-labelledby.
  • Required fields marked in text, not color alone. Use aria-required="true" or the required attribute.
  • Error text in prose, linked via aria-describedby and aria-invalid="true" on the field.
  • Error summary at the top on submit failure, with links or focus moves to the first invalid field.
  • Authentication must not rely on memory alone. 3.3.8 Accessible Authentication (Minimum) allows copy-paste, password managers, and WebAuthn. Do not block paste on password fields.
Accessible field error pattern
<label for="email">Email</label>
<input
  type="email"
  id="email"
  name="email"
  aria-describedby="email-error"
  aria-invalid="true"
  autocomplete="email"
/>
<p id="email-error" role="alert">
  Enter a valid email address, for example [email protected].
</p>

For 3.2.6 Consistent Help, put contact, FAQ, and support links in the same place on every page (footer or header). Agents scatter "Need help?" links randomly across templates.

CH 05

Testing with axe-core.

Automated accessibility testing catches the boring failures agents repeat: missing labels, bad contrast, broken ARIA. Three tools, one engine: axe-core.

1. Browser extension for spot checks during dev. Deque axe DevTools or the open-source extension. Run it on every new page before you merge.

2. @axe-core/playwright for e2e. Scan rendered pages in CI after your app mounts. Catches issues unit tests miss because they never render CSS.

3. jest-axe for component tests. Fast feedback on individual React/Vue/Svelte components without launching a browser.

$ Playwright + @axe-core/playwright
npm install -D @axe-core/playwright

// e2e/a11y.spec.ts
import { test, expect } from '@playwright/test';
import AxeBuilder from '@axe-core/playwright';

test('home page has no critical a11y violations', async ({ page }) => {
  await page.goto('/');
  const results = await new AxeBuilder({ page })
    .withTags(['wcag2a', 'wcag2aa', 'wcag22aa'])
    .analyze();
  expect(results.violations.filter(v =>
    ['critical', 'serious'].includes(v.impact)
  )).toEqual([]);
});
$ jest-axe component test
npm install -D jest-axe

import { render } from '@testing-library/react';
import { axe, toHaveNoViolations } from 'jest-axe';
import { LoginForm } from './LoginForm';

expect.extend(toHaveNoViolations);

test('LoginForm is accessible', async () => {
  const { container } = render(<LoginForm />);
  const results = await axe(container);
  expect(results).toHaveNoViolations();
});

axe cannot judge alt text quality or logical reading order. After automated scans, tab through the page once with your keyboard. That 10-minute pass catches what machines miss. See Guide 09 for where a11y tests fit in the broader pyramid.

CH 06

The CI gate.

Prompts drift. CI does not. Add an accessibility job that runs on every PR touching src/, app/, or components/. Fail on critical and serious axe violations. Warn on moderate until you burn down the backlog.

.github/workflows/a11y.yml
name: Accessibility
on:
  pull_request:
    paths: ['src/**', 'app/**', 'components/**']
jobs:
  axe:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - uses: actions/setup-node@v4
        with: { node-version: '22' }
      - run: npm ci
      - run: npm run build
      - run: npx playwright install --with-deps chromium
      - run: npm run test:a11y

Pair the CI job with the AGENTS.md contract and the frontend-accessibility skill. When the agent knows CI will reject div-buttons, it stops generating them. Same pattern as type-check gates in Guide 01.

Optional but high leverage: a pre-commit hook that runs jest-axe on changed component files. Fast feedback before the PR even opens.

DEMO · INTERACTIVE

Live: WCAG 2.2 AA audit checklist.

Twelve checks before an AI-built UI meets real users. Tap each one as you confirm it. Your score saves in this browser so you can resume the audit tomorrow.

WCAG 2.2 AA audit Saves to your browser · No network calls
Score
0
Not ready. Fix semantic HTML and contrast first.

0–5: do not ship to users. 6–9: ok for internal demos. 10–11: ok for public beta. 12: ok for production and compliance conversations.

PITFALLS

Common ways this still goes wrong.

ARIA as a band-aid

Agents add aria-label to unlabeled icon buttons instead of visible text or a proper label element. Screen reader users get "Delete" with no context about what is being deleted. Fix the design, not the attribute.

Automated green, manual red

CI passes axe but keyboard users cannot reach the submit button because a z-index stack traps focus in a hidden layer. Run one manual Tab walkthrough per feature. axe will never catch that.

Accessibility overlay widgets

Third-party "accessibility plugins" that add a toolbar to your site do not fix WCAG violations and often create new ones. They are a liability, not a shortcut. Fix the HTML.

Fixing a11y in a follow-up PR

"Ship the feature, then add accessibility" doubles the work and triples the regressions. Put the contract in AGENTS.md before the first UI prompt. See Guide 13 for getting the spec right the first time.

What to read next.

  • Guide Prompting for UI Code Put accessibility in the same prompt block as states, tokens, and component contracts.
  • Guide Testing AI-Written Code Where jest-axe and Playwright a11y scans fit in your test pyramid.
  • Skill frontend-accessibility SKILL.md Install in your AI tool for WCAG 2.2 AA patterns on every UI task.
Changelog
  • 2026-06-15Initial publish. Targets WCAG 2.2 Level AA with 2.4.11, 2.5.8, 3.3.8, and 3.2.6 called out explicitly.
STATUS ● BUILDING THE FUTURE
MISSION LLM RESOURCES
VERSION BETA 3.0

BUILD WITH AI. SHIP WITH CONFIDENCE.

@WEBDEVELOPERHQ ↗
TERMS / PRIVACY
FRIENDS
Authentic Jobs
Authentic Jobs ↗
Web Reference
Web Reference ↗
Ready.dev
Ready.dev ↗
Design.dev
Design.dev ↗
© 2026 WEB DEVELOPER / ALL RIGHTS RESERVED