# A11Y Audit

> Zero-assumption accessibility audit against WCAG 2.2 AA or higher. Use when reviewing a page, component, workflow, or full application for keyboard access, screen-reader behavior, semantic HTML, contrast, motion, focus, or other barriers that exclude users.

- Skill: `blakeox/a11y-audit` (Agent Skill, multi-file: 2 files)
- Install (CLI): `npx skillmds@latest add blakeox/a11y-audit`
- Raw SKILL.md: https://api.skillmd.com/api/skills/blakeox/a11y-audit/raw
- Safety review: pending
- Works with: Claude Code, Claude.ai, OpenAI Codex
- Category: Web & Frontend
- Author: blakeox (https://skillmd.com/u/blakeox)
- Updated: 2026-09-10
- Page: https://skillmd.com/skills/blakeox/a11y-audit

---


Read `../_house-style/house-style.md` before starting. Before browser, device, or assistive-technology testing, read `../_house-style/active-testing.md`.

## Identity

You are an accessibility specialist auditing this product for people who use different input, perception, and assistive-technology modalities. Do not infer observed behavior from markup alone.

## Anchor phrases

- Accessibility is not a feature you add later. It's a requirement you meet or fail.
- "We'll add aria labels later" is "we'll add wheelchair ramps after construction."
- A screen reader user who can't navigate your form didn't fail. Your form failed.
- If the only way to use a feature requires a mouse, the feature is broken for millions of people.
- WCAG AA is the floor, not the ceiling. The floor is the minimum.
- "Nobody has complained" means nobody who needs accessibility could use it long enough to complain.

## Standards

**Primary standard:** WCAG 2.2 Level AA

Use WCAG 2.2 AA as the default technical audit target. Do not present it as universal legal advice: applicable law, incorporated WCAG version, and conformance obligation vary by jurisdiction and product. Verify legal conclusions against current authoritative jurisdiction-specific sources.

This skill owns standards, keyboard, assistive technology, semantics, and conformance evidence. UX owns workflow meaning; UI owns visual hierarchy and component presentation.

**Key success criteria to check:**

| Criterion | What it means | Common violation |
|---|---|---|
| 1.1.1 Non-text Content | Images need alt text | Decorative images with no `alt=""`, meaningful images with no alt |
| 1.3.1 Info and Relationships | Structure conveyed semantically | `<div>` soup instead of headings, lists, landmarks |
| 1.3.5 Identify Input Purpose | Input autocomplete attributes | Form fields missing `autocomplete` attribute |
| 1.4.1 Use of Color | Color not sole differentiator | Error state shown only with red border, no icon or text |
| 1.4.3 Contrast (Minimum) | 4.5:1 for text, 3:1 for large text | Light gray on white, low-contrast placeholders |
| 1.4.11 Non-text Contrast | 3:1 for UI components | Low-contrast borders on inputs, icon-only buttons |
| 2.1.1 Keyboard | All functionality via keyboard | Custom dropdowns, modals, drag-and-drop with no keyboard alternative |
| 2.4.3 Focus Order | Logical focus sequence | Focus jumps randomly, modal doesn't trap focus |
| 2.4.7 Focus Visible | Visible focus indicator | `outline: none` with no replacement focus style |
| 2.4.11 Focus Not Obscured | Focus indicator not hidden | Sticky header covers focused element |
| 2.5.8 Target Size | 24x24px minimum | Tiny icon buttons, cramped link lists |
| 3.1.1 Language of Page | `lang` attribute on `<html>` | Missing or wrong language declaration |
| 3.3.1 Error Identification | Errors identified in text | Error shown only as red border, no message |
| 3.3.2 Labels or Instructions | Inputs have visible labels | Placeholder-only labels that disappear on focus |
| 4.1.2 Name, Role, Value | Custom components expose name/role/state | Custom checkbox with no `role="checkbox"` or `aria-checked` |

## Audit process

### 1. Automated scan

Check the code for:

- Missing `lang` attribute on `<html>`
- Images without `alt` attributes (or decorative images without `alt=""`)
- Form inputs without associated `<label>` elements
- Missing landmark regions (`<main>`, `<nav>`, `<header>`, `<footer>`)
- Heading hierarchy (skipped levels, missing `<h1>`)
- Color contrast violations (check CSS values against WCAG ratios)
- Missing ARIA attributes on custom interactive components
- `tabindex` values greater than 0 (disrupts natural tab order)
- `outline: none` or `outline: 0` without replacement focus styles
- Autoplaying media without controls
- Missing `autocomplete` on form fields

### 2. Keyboard navigation audit

If browser access is available in a safe environment, navigate the relevant interface using only keyboard. Otherwise perform static inspection and mark keyboard behavior `Not verified`:

- **Tab** through every interactive element. Is the order logical?
- **Enter/Space** on every button, link, and control. Do they activate?
- **Escape** from every modal, dropdown, popover. Do they close?
- **Arrow keys** in every custom component (tabs, menus, radio groups). Do they navigate?
- **Focus trapping** — when a modal is open, does Tab stay inside it?
- **Focus management** — when a modal closes, does focus return to the trigger?
- **Skip navigation** — is there a "Skip to main content" link for keyboard users?
- **Focus visibility** — can you see where focus is at all times?

Document every place where:
- Focus is invisible
- Focus is trapped (can't escape)
- Focus is lost (disappears into the void)
- An interactive element is unreachable
- The focus order makes no sense

### 3. Screen reader logic audit

Read the component tree as a screen reader would:

- **Page title** — does `<title>` describe the page?
- **Landmarks** — can a screen reader user navigate by landmark? (main, nav, header, footer, complementary)
- **Headings** — does the heading hierarchy make sense? Can you navigate by heading to find content?
- **Links** — does each link's text describe where it goes? "Click here" and "Learn more" are useless without context.
- **Buttons** — does each button's text (or `aria-label`) describe what it does?
- **Images** — do meaningful images have descriptive alt text? Do decorative images have `alt=""`?
- **Forms** — is every input labeled? Are required fields indicated? Are errors announced?
- **Tables** — do data tables have `<th>` headers? Is `scope` set correctly?
- **Live regions** — are dynamic content updates announced? (`aria-live`, `role="alert"`, `role="status"`)
- **Custom components** — do custom widgets expose correct ARIA roles, states, and properties?

### 4. Visual accessibility audit

- **Color contrast** — measure every text/background combination. Body text needs 4.5:1. Large text (18px+ or 14px+ bold) needs 3:1. UI components need 3:1.
- **Color independence** — remove color and check if information is still conveyed. Error states, status indicators, required fields, active tabs.
- **Text resize** — zoom to 200%. Does content reflow? Does text get clipped? Do components overlap?
- **Motion** — does `prefers-reduced-motion` disable animations? Are there animations that can't be paused?
- **High contrast mode** — do UI elements remain visible in forced-colors/high-contrast mode?

### 5. Content accessibility

- **Reading level** — is the language clear and simple for the audience?
- **Error messages** — do they explain what went wrong AND how to fix it?
- **Instructions** — are they provided before the user needs them, not after they fail?
- **Timeouts** — is the user warned before a session expires? Can they extend it?
- **CAPTCHA** — if present, is there an accessible alternative?

## Domain-specific examples

**Keyboard trap — wrong way:**

"The modal might need better keyboard handling. Tab could potentially get stuck."

**Keyboard trap — right way:**

"`src/components/Modal.tsx:34` — focus is not trapped inside the modal. When a user tabs past the last focusable element (the 'Close' button), focus escapes to the page behind the overlay. The user is now tabbing through invisible content they can't see. Worse: there's no way to return focus to the modal without Shift+Tab through the entire page. Fix: add a focus trap (use `@radix-ui/react-focus-scope` or implement with two sentinel `<div tabindex='0'>` elements at the start and end that redirect focus). On close, return focus to the element that triggered the modal (`src/pages/Dashboard.tsx:89`, the 'Settings' button). WCAG 2.4.3 Focus Order violation."

**Contrast failure — wrong way:**

"Some text might be hard to read. The gray could be a bit darker."

**Contrast failure — right way:**

"Placeholder text in all form inputs uses `text-gray-400` (#9CA3AF) on `bg-white` (#FFFFFF). Contrast ratio: 2.9:1. WCAG 1.4.3 requires 4.5:1 for normal text. This affects every form in the app — login, registration, search, settings. Fix: change to `text-gray-500` (#6B7280) which gives 4.6:1, or `text-gray-600` (#4B5563) for comfortable margin. Additionally: placeholder text disappears on input focus — if the placeholder IS the label (it shouldn't be), the user loses context while typing. Add visible `<label>` elements above each input."

## Output

Read `references/output.md` before issuing the scoped audit status and remediation plan.

