WCAG 2.2 Accessibility Audit skill for Codex
Findings mapped to criteria, with fixes. Codex runs a real accessibility audit instead of quoting a Lighthouse score: it scopes representative templates and journeys, scans every UI state with axe-core, then does keyboard, screen reader, zoom, contrast and form passes covering the new 2.2 criteria. Findings carry a success criterion, evidence, affected users and a code fix, and the report states what was not tested.
curl -fsSL codexskills.dev/install.sh \ | sh -s -- wcag-accessibility-audit Installs to ~/.agents/skills/wcag-accessibility-audit/ for every project on this machine.
curl -fsSL codexskills.dev/cli.mjs -o codex-skills.mjsnode codex-skills.mjs install wcag-accessibility-audit The CLI also searches, updates and removes skills. Run it with no arguments to pick from a list. Needs Node 20.12+.
curl -fsSL codexskills.dev/install.sh \ | sh -s -- --project wcag-accessibility-audit Installs to .agents/skills/wcag-accessibility-audit/ in the current repository. Commit it and everyone who runs Codex there gets it.
curl -fsSLO codexskills.dev/downloads/wcag-accessibility-audit.zipunzip -o wcag-accessibility-audit.zip -d ~/.agents/skills/ The archive holds the skill folder with its generated agents/openai.yaml. Unzip into .agents/skills/ to scope it to a repo.
& ([scriptblock]::Create((irm codexskills.dev/install.ps1))) wcag-accessibility-audit PowerShell 5.1 or 7. Installs to %USERPROFILE%\.agents\skills\. Add -Project for the current repo.
104-line POSIX script. No sudo, no telemetry. Read install.sh
Use it in Codex
Start a prompt with $wcag-accessibility-audit, or pick it from /skills.
You can also just describe the task: Codex reads the description below and loads the playbook when your request matches.
What Codex reads before loading it
Audits web pages and UI components against WCAG 2.2 Level AA - automated axe-core scan, then manual keyboard, screen reader, zoom/reflow, contrast and forms passes - and produces a findings report mapped to success criteria with severity, evidence and code fixes. Use when a user asks for an accessibility audit, a WCAG or ADA/EAA compliance check, a VPAT/ACR input, a review of a page or component for screen reader and keyboard users, or wants to know which WCAG 2.2 criteria a UI fails.
Use it when
- A page, flow or component library needs a WCAG 2.2 AA assessment
- Preparing evidence for an accessibility conformance report or legal review
- Users report they cannot complete a task with a keyboard or screen reader
Use something else when
- Wiring automated axe checks into the test suite: use accessibility-testing
- Implementing one specific widget correctly: use aria-patterns
- Only checking color contrast of a palette: use color-contrast-accessibility
How to check the result
Every finding cites a real success criterion with evidence and a fix, the coverage table accounts for all A and AA criteria, and fixed issues are re-tested with the method that found them.
The mistake it prevents
Calling an automated scan of the initial page load an audit; most failures only appear in keyboard, screen reader and error-state testing.
Done well means: every finding cites a success criterion, shows evidence (selector, screenshot or reproduction steps), states who is affected, and has a concrete fix; the report says what was tested, how, and what was not covered. Automated tools find roughly a third of issues; an audit without the manual passes is a scan, and must be labeled as one.
Legal compliance (ADA, Section 508, EN 301 549 / European Accessibility Act) is a legal judgment. Report against WCAG and say plainly that a legal or accessibility specialist should confirm conformance claims and any VPAT/ACR before publication.
Scope first
Agree on this before testing and write it at the top of the report:
- Pages or components in scope (pick representative templates: home, list, detail, form, checkout, auth, error page), plus key user journeys.
- Target: WCAG 2.2 Level A + AA (default). AAA only if asked.
- Environments: browsers and screen readers (default: Chrome + NVDA or JAWS on Windows, Safari + VoiceOver on macOS/iOS, Chrome + TalkBack on Android when mobile is in scope).
- Test accounts and data. Use the project’s seed or fixture credentials for local or staging environments; never sign in to production with real user data for an audit.
Workflow
- [ ] 1. Read AGENTS.md; find how to run the app locally and any existing a11y tests
- [ ] 2. Automated scan of each page and state (axe), save raw results
- [ ] 3. Keyboard pass
- [ ] 4. Screen reader pass on key journeys
- [ ] 5. Visual pass: zoom 200%, reflow at 320 CSS px, text spacing, contrast, motion
- [ ] 6. Forms and errors pass
- [ ] 7. Code review of semantics (landmarks, headings, names, ARIA usage)
- [ ] 8. De-duplicate, map to criteria, assign severity, write fixes
- [ ] 9. Report (template below); re-test fixes if asked
1. Automated scan
Use what the project has; otherwise a one-off Playwright script (packages need network):
// a11y-scan.spec.ts — npx playwright test a11y-scan.spec.ts
import { test, expect } from "@playwright/test";
import AxeBuilder from "@axe-core/playwright";
for (const path of ["/", "/products", "/checkout"]) {
test(`axe ${path}`, async ({ page }) => {
await page.goto(path);
const results = await new AxeBuilder({ page })
.withTags(["wcag2a", "wcag2aa", "wcag21a", "wcag21aa", "wcag22aa"])
.analyze();
console.log(JSON.stringify(results.violations, null, 2));
expect(results.violations).toEqual([]);
});
}
- Scan every state, not just page load: open menus, dialogs, validation errors, empty and loading states. Run axe again after each interaction.
- Treat axe “incomplete” results as manual checks to do, not passes.
- Lighthouse’s accessibility score is a subset of axe; never cite it as conformance.
2. Keyboard pass
Unplug the mouse mentally. For each page:
- Tab through everything: every interactive element is reachable, in a logical order, with a clearly visible focus indicator (2.1.1, 2.4.3, 2.4.7).
- Focused element is not hidden behind sticky headers, cookie banners or chat widgets (2.4.11 Focus Not Obscured, new in 2.2).
- No traps: you can leave every widget, including embedded iframes and modals (2.1.2).
- Composite widgets follow expected keys (arrows in menus/tabs/listboxes, Escape closes,
Enter/Space activate); compare against
$aria-patterns. - Modals move focus in, keep it in, and return it to the trigger on close.
- A skip link or landmarks let users bypass repeated navigation (2.4.1).
- Single-character shortcuts can be turned off or remapped (2.1.4).
3. Screen reader pass
Run the main journeys end to end. Check that:
- Page
<title>and theh1identify the page; headings form an outline (1.3.1, 2.4.2, 2.4.6). - Landmarks exist (
header,nav,main,footer) and are labeled when repeated. - Every control announces name, role, state and value (4.1.2); icon buttons are not “button”.
- Images have meaningful alt, decorative images are silent (1.1.1).
- Dynamic updates (toasts, cart count, search results, errors) are announced (4.1.3).
- Reading order matches visual order (1.3.2).
4. Visual pass
- Zoom to 200%: no loss of content or function (1.4.4). At 1280px wide and 400% zoom (320 CSS px) content reflows to one column with no horizontal scroll, except tables, maps and similar (1.4.10).
- Text spacing override (line-height 1.5, paragraph 2x, letter 0.12em, word 0.16em) does not clip text (1.4.12).
- Contrast: text 4.5:1, large text (24px, or 18.66px bold) 3:1, UI component boundaries, focus indicators and meaningful icons 3:1 (1.4.3, 1.4.11). Check hover, focus, disabled- looking-but-enabled and placeholder states. Color is never the only cue (1.4.1).
- Pointer targets are at least 24x24 CSS px or have enough spacing (2.5.8, new in 2.2).
- Content on hover/focus (tooltips) is dismissible, hoverable and persistent (1.4.13).
- Motion: no flashing more than 3 times per second (2.3.1); auto-moving content over 5 s can be paused (2.2.2); orientation not locked (1.3.4).
5. Forms and errors
- Every field has a programmatic label; placeholder is not a label (1.3.1, 3.3.2, 2.5.3 label in name matches visible text).
- Required fields and formats are stated before submission; errors identify the field and describe the fix in text (3.3.1, 3.3.3); errors on legal or financial submissions can be reviewed or reversed (3.3.4).
- Personal data fields have correct
autocompletetokens (1.3.5). - Information already entered in the same process is not asked for again (3.3.7, new in 2.2).
- Login does not require a cognitive test such as transcribing or remembering; paste and password managers work (3.3.8, new in 2.2).
- Help mechanisms (contact, chat) appear in the same relative place on each page (3.2.6).
- Drag-only interactions have a single-pointer alternative (2.5.7, new in 2.2).
For the full A/AA criterion list with test methods, read references/wcag-22-aa-checklist.md when building the report’s coverage table or when a finding’s criterion is unclear.
Severity
| Severity | Meaning |
|---|---|
| Critical | Blocks a task for a user group (keyboard trap, unlabeled submit, inaccessible checkout step) |
| Serious | Task possible but very hard or error-prone (missing error association, focus lost after dialog) |
| Moderate | Friction or confusion (heading levels skipped, vague link text) |
| Minor | Polish; low impact (redundant title attribute) |
Group repeated issues from a shared component into one finding with all locations listed; fixing the component fixes them all.
Gotchas
- Do not “fix” with ARIA what native HTML solves;
role="button"on adivstill needs keyboard handling. First rule of ARIA: use the native element. aria-hidden="true"on a focusable element creates a ghost focus stop; check with Tab.- Overlay widgets that claim to fix accessibility automatically do not produce conformance; note them in the report if present.
outline: nonewith no replacement is the most common 2.4.7 failure; check:focus-visible.- An axe pass on the initial load says nothing about the error state of a form.
- 4.1.1 Parsing is obsolete in WCAG 2.2; do not report it.
Report template
# Accessibility audit: <product>, <date>
**Standard:** WCAG 2.2 Level AA **Method:** axe-core 4.x + manual (keyboard, NVDA 2025.x/Chrome, VoiceOver/Safari)
**Scope:** /, /search, /product/:id, /checkout (guest), sign-in **Not tested:** PDFs, admin area
**Summary:** 4 critical, 7 serious, 9 moderate, 3 minor. Checkout cannot be completed by keyboard.
| # | Severity | WCAG SC | Location | Issue | Who is affected | Fix |
|---|---|---|---|---|---|---|
| 1 | Critical | 2.1.2 No Keyboard Trap | /checkout, address modal | Focus cannot leave the postcode lookup iframe | Keyboard, switch users | Close lookup on Escape; return focus to field |
### Finding 1 details
Steps to reproduce, evidence (selector, screenshot), code fix (diff), how to re-test.
**Coverage:** table of criteria: Pass / Fail / Not applicable / Not tested
**Note:** This is a technical assessment against WCAG, not legal advice on compliance.
Verification
- Every finding has an SC number, evidence and a fix; no SC cited that does not exist.
- Each fixed issue was re-tested with the same method that found it (axe re-run plus the manual step).
- The coverage table accounts for every A and AA criterion.
What the zip contains
Codex reads bundled files only when a step points to them. Every one is shown here, so you can review it before installing.
agents/openai.yaml
Gives the Codex app a display name, a one-line description and a starter prompt for this skill.
interface:
display_name: "WCAG 2.2 Accessibility Audit"
short_description: "Findings mapped to criteria, with fixes"
default_prompt: "$wcag-accessibility-audit Audit our checkout flow against WCAG 2.2 AA and give me a prioritized report" references/wcag-22-aa-checklist.md
# WCAG 2.2 Level A and AA: test checklist
55 success criteria (31 A, 24 AA). 4.1.1 Parsing was removed in 2.2 and is not listed.
"Auto" means axe-core can flag some failures; every row still needs the manual check.
Paraphrased for testing; cite the official criterion name in reports.
## 1 Perceivable
| SC | Level | Name | How to test |
|---|---|---|---|
| 1.1.1 | A | Non-text Content | Auto + manual: informative images have alt that conveys purpose; decorative ones `alt=""`; icon buttons, charts, CAPTCHAs have text alternatives |
| 1.2.1 | A | Audio-only and Video-only (Prerecorded) | Transcript for audio-only; transcript or audio track for silent video |
| 1.2.2 | A | Captions (Prerecorded) | Captions present, synchronized, include speakers and meaningful sounds |
| 1.2.3 | A | Audio Description or Media Alternative (Prerecorded) | Visual-only information in video is described or a full text alternative exists |
| 1.2.4 | AA | Captions (Live) | Live streams have real-time captions |
| 1.2.5 | AA | Audio Description (Prerecorded) | Audio description track for visual information not in the dialogue |
| 1.3.1 | A | Info and Relationships | Auto + manual: headings, lists, tables (`th`, scope), labels, fieldset/legend and landmarks expressed in markup, not only styling |
| 1.3.2 | A | Meaningful Sequence | DOM/reading order matches visual order; check CSS order, grid placement, absolute positioning |
| 1.3.3 | A | Sensory Characteristics | Instructions do not rely only on shape, position, color or sound ("click the round button on the right") |
| 1.3.4 | AA | Orientation | Works in portrait and landscape; no orientation lock unless essential |
| 1.3.5 | AA | Identify Input Purpose | Auto: user-data fields have correct `autocomplete` tokens (`email`, `given-name`, `street-address`, `one-time-code`) |
| 1.4.1 | A | Use of Color | Links in body text, errors, required fields, chart series are distinguishable without color |
| 1.4.2 | A | Audio Control | Audio that autoplays more than 3 s can be paused or muted independently |
| 1.4.3 | AA | Contrast (Minimum) | Auto + manual: 4.5:1 text, 3:1 large text; check states, text over images and gradients |
| 1.4.4 | AA | Resize Text | Browser zoom 200%: nothing clipped, overlapping or lost |
| 1.4.5 | AA | Images of Text | Real text used instead of text baked into images, except logos |
| 1.4.10 | AA | Reflow | 320 CSS px width (1280 px at 400%): single column, no two-dimensional scrolling except for data tables, maps, diagrams |
| 1.4.11 | AA | Non-text Contrast | 3:1 for input borders, focus rings, toggles, checkbox outlines, meaningful icons and chart elements against adjacent colors |
| 1.4.12 | AA | Text Spacing | Apply spacing bookmarklet (1.5 line, 2x paragraph, 0.12em letter, 0.16em word): no clipped or overlapping text |
| 1.4.13 | AA | Content on Hover or Focus | Tooltips/popovers: dismiss with Escape without moving pointer, pointer can move onto them, stay until dismissed |
## 2 Operable
| SC | Level | Name | How to test |
|---|---|---|---|
| 2.1.1 | A | Keyboard | Every function works with keyboard alone; no hover-only or drag-only controls |
| 2.1.2 | A | No Keyboard Trap | Focus can always move away (including iframes, editors, modals by Escape or close button) |
| 2.1.4 | A | Character Key Shortcuts | Single-key shortcuts can be turned off, remapped, or only work when the component has focus |
| 2.2.1 | A | Timing Adjustable | Session timeouts warn and allow extension (at least 20 s to respond), unless real-time or over 20 h |
| 2.2.2 | A | Pause, Stop, Hide | Carousels, marquees, auto-updating feeds lasting over 5 s have pause or stop controls |
| 2.3.1 | A | Three Flashes or Below Threshold | No content flashes more than 3 times per second |
| 2.4.1 | A | Bypass Blocks | Skip link to main content works, or landmarks/headings allow bypassing |
| 2.4.2 | A | Page Titled | Unique, descriptive `<title>`, updated on SPA route change |
| 2.4.3 | A | Focus Order | Tab order preserves meaning; dialogs receive focus; no positive `tabindex` chaos |
| 2.4.4 | A | Link Purpose (In Context) | Link text plus its sentence, paragraph or list item explains the destination; no bare "click here" lists |
| 2.4.5 | AA | Multiple Ways | At least two ways to reach pages (nav, search, sitemap), except steps in a process |
| 2.4.6 | AA | Headings and Labels | Headings and labels describe topic or purpose |
| 2.4.7 | AA | Focus Visible | Visible focus indicator on every focusable element |
| 2.4.11 | AA | Focus Not Obscured (Minimum) | Focused element is not entirely hidden by sticky headers, footers, banners or chat widgets |
| 2.5.1 | A | Pointer Gestures | Multipoint or path gestures (pinch, swipe) have single-pointer alternatives (buttons) |
| 2.5.2 | A | Pointer Cancellation | Actions fire on up-event, or can be aborted by moving off before release |
| 2.5.3 | A | Label in Name | Accessible name contains the visible label text (voice control users say what they see) |
| 2.5.4 | A | Motion Actuation | Shake/tilt functions have UI alternatives and can be disabled |
| 2.5.7 | AA | Dragging Movements | Drag operations (sortable lists, sliders, maps) have a single-pointer non-drag alternative |
| 2.5.8 | AA | Target Size (Minimum) | Targets at least 24x24 CSS px, or spaced so a 24 px circle around each does not overlap another; inline links and native controls exempt |
## 3 Understandable
| SC | Level | Name | How to test |
|---|---|---|---|
| 3.1.1 | A | Language of Page | Auto: `<html lang>` present and correct |
| 3.1.2 | AA | Language of Parts | Passages in another language carry `lang` |
| 3.2.1 | A | On Focus | Focusing an element does not submit, navigate or open a new window |
| 3.2.2 | A | On Input | Changing a select or checkbox does not trigger unexpected context changes without warning |
| 3.2.3 | AA | Consistent Navigation | Repeated navigation keeps the same relative order across pages |
| 3.2.4 | AA | Consistent Identification | Same function, same name and icon across pages |
| 3.2.6 | A | Consistent Help | Help links, contact details or chat appear in the same relative order on each page that has them |
| 3.3.1 | A | Error Identification | Errors are described in text and the field is identified |
| 3.3.2 | A | Labels or Instructions | Labels and required-format hints present before input |
| 3.3.3 | AA | Error Suggestion | Error messages say how to fix the problem when known |
| 3.3.4 | AA | Error Prevention (Legal, Financial, Data) | Submissions are reversible, checked, or confirmed before final commit |
| 3.3.7 | A | Redundant Entry | Previously entered info in the same process is auto-filled or selectable, unless re-entry is essential (e.g. password confirmation for security) |
| 3.3.8 | AA | Accessible Authentication (Minimum) | No cognitive function test to log in, unless an alternative, a mechanism to assist (paste, password manager, show password), or object/personal-content recognition is offered |
## 4 Robust
| SC | Level | Name | How to test |
|---|---|---|---|
| 4.1.2 | A | Name, Role, Value | Auto + screen reader: custom widgets expose role, name, state (`aria-expanded`, `aria-checked`, `aria-selected`) and update them |
| 4.1.3 | AA | Status Messages | Toasts, result counts, "saved", cart updates announced via `role="status"`/`aria-live` without moving focus |
## Coverage table format
```markdown
| SC | Result | Notes |
|---|---|---|
| 1.1.1 | Fail | Findings 3, 8 |
| 1.2.2 | Not applicable | No video content in scope |
| 2.5.8 | Pass | Spot-checked 40 controls at 1x zoom |
| 1.4.10 | Not tested | Checkout iframe provided by payment vendor |
```
About this skill
Written and maintained by the Codex Skills editors to the same editorial standard as every skill here: a selective description, a real workflow, known pitfalls and a way to verify the work. First published , last updated . MIT licensed. Something wrong or out of date? Tell us.