wcag-auditor
Use when the user wants to audit their own web application for WCAG accessibility compliance, run an a11y check on a URL (including authenticated routes), generate a markdown accessibility findings report, fill in a VPAT / ACR, or get concrete remediation diffs for accessibility violations. Default target is WCAG 2.2 Level AA (covers 2.0/2.1 A/AA transitively); AAA is opt-in. VPAT fill-in requires a user-supplied ITI VPAT 2.5 INT template; covers WCAG + Section 508 + EN 301 549 via crosswalks.
WCAG Auditor
Audit the user's own web application for WCAG 2.2 accessibility compliance. Runs axe-core via Playwright against public and authenticated routes, maps violations to specific WCAG success criteria, and proposes concrete Edit-tool diffs against the user's source code.
Prerequisites
- Node.js ≥ 22 (current active LTS). The scripts use modern ESM features and will fail on Node 20. This is the only thing the user must install themselves.
- Runtime dependencies (
playwright,@axe-core/playwright,axe-core,fast-xml-parser,docx,jszip) and the Playwright Chromium binary are installed automatically by Claude on first use — see the bootstrap step at the top of the workflow. The user does not run anything manually.
When to use this skill
Invoke when the user says any of:
- "audit my site for accessibility"
- "check WCAG compliance" / "run an a11y check"
- "find accessibility bugs in <URL>"
- "generate a VPAT" / "fill in an ACR" / "make an accessibility conformance report"
- "what accessibility issues does <URL> have?"
- "make my site WCAG-compliant"
Do not use this skill for:
- Heuristic UI/UX review unrelated to WCAG — use
web-design-guidelinesinstead - Accessibility of native mobile apps, desktop apps, or PDFs — this skill audits web content only
- Auditing sites the user does not own, where no source is available — the skill relies on reading the user's repo to propose fixes
Workflow
0. Bootstrap (auto, on first use and after plugin updates)
Run this before step 1 every time the skill is invoked. It is a cheap
no-op when dependencies are already installed, and it self-heals after
plugin version bumps (which land the skill in a new versioned cache dir
without node_modules/).
Determine the skill install dir — typically the directory that contains
this SKILL.md. In a plugin install that will be something like:
~/.claude/plugins/cache/wcag-auditor-tools/wcag-auditor/<version>/skills/wcag-auditor/
The skill's package.json lives at the plugin root (two levels up
from skills/wcag-auditor/). From now on, use <PLUGIN_ROOT> to refer to
that directory. Do not cd into it — cd can be fragile across Claude
Code shells (especially on Windows where paths contain spaces and
backslashes). Instead, pass the plugin root as an absolute path to every
command, either via --prefix (for npm) or as part of the argument path.
-
If
<PLUGIN_ROOT>/node_modules/does not exist, install dependencies. Use--prefixso the command works regardless of current working directory and without needing acd:npm install --prefix "<PLUGIN_ROOT>"This installs
playwright,@axe-core/playwright,axe-core,fast-xml-parser,docx, andjszip. Tarballs are npm-cached globally, so subsequent installs after plugin updates are fast. -
Install the Playwright Chromium binary. Always invoke Playwright's CLI entry point directly via
node, not vianpx playwrightand never vianode_modules/.bin/playwright— the.bin/shim is a bash script on Unix and a.cmdon Windows, and trying to run it withnode <path-to-shim>on Windows fails with aSyntaxError: missing ) after argument list(Node parses the bash shebang script as JavaScript). The CLI entry point is an ESM file and is fully cross-platform:node "<PLUGIN_ROOT>/node_modules/playwright/cli.js" install chromiumExits in under a second with "chromium X is already installed" when the correct revision is cached (
~/Library/Caches/ms-playwright/on macOS,~/.cache/ms-playwright/on Linux,%LOCALAPPDATA%\ms-playwright\on Windows). Downloads only when Playwright version changed.
Both commands are idempotent. If the user has not pre-authorized npm /
node in their Claude Code permissions, the first invocation prompts for
approval; after that they run silently.
Do not proceed to step 1 until both commands complete successfully
(exit 0). If npm install fails (e.g., offline, disk full), surface
the error and stop — the audit scripts cannot run without dependencies.
Platform note: on Windows, <PLUGIN_ROOT> will look like
C:\Users\<Name>\.claude\plugins\cache\wcag-auditor-tools\wcag-auditor\<version>.
Always quote the path when passing to --prefix or as an argument, since
it almost always contains spaces (e.g., OneDrive-synced home directories).
1. Clarify scope (mandatory gate)
You MUST confirm all scope parameters with the user via the AskUserQuestion
tool before running any audit, login, or score script. This holds even when
the user's prompt appears complete and even when defaults seem obviously
correct — the user has explicitly opted into confirming defaults rather than
having them silently assumed.
The only exception: if the user has said in this conversation "use defaults, don't ask" (or equivalent), skip to step 2.
Use AskUserQuestion to confirm each of the following. Pre-fill the option
list with the inferred or default value marked, so the user can accept with
one click:
- Target URL(s) — the URL, dev server, or staging origin to audit. If the user provided one, present it as the default option alongside "something else". If not, this is a free-text answer.
- WCAG level — options:
AA (default),AAA (opt-in, aspirational). - Auth required — options:
No, public routes only,Yes, capture session via login.mjs. - Viewports — options:
Desktop 1280×800 + mobile 375×667 (default),Custom. - Framework — only ask if the framework is not unambiguously detectable
from the repo (
package.json, file layout). If detection is clear, state the detected framework in your next message and proceed without asking.
Do not start step 2 until every applicable question above has been answered.
2. Discover URLs
Consult references/url-discovery.md. Detect the framework from the user's
repo (package.json, file layout), run the appropriate recipe to extract a
route list, resolve any dynamic segments ([id], :id) to concrete URLs
using real test-user IDs, and produce urls.txt. Show the list for review
before auditing.
For authenticated SaaS routes, sitemaps are insufficient — the framework recipe is essential.
3. Set up auth (if needed)
node scripts/login.mjs --url <login-url> --out ./auth.json
Playwright opens a headed browser; the user completes login (including MFA)
and presses Enter in the terminal. Session state saves to ./auth.json.
Important caveats to communicate:
auth.jsoncontains live session credentials — already gitignored, never commit it- Session expires when the user's app session expires; re-run
login.mjswhen needed - CSRF tokens with short lifetimes may require re-login
4. Run the automated audit
All outputs default to ./wcag-audit/ in the current working directory —
a single workspace directory keeps audit artifacts together and easy to
.gitignore. Override with --out if needed.
Single URL:
node scripts/audit.mjs --url <url> --auth ./auth.json
# writes ./wcag-audit/audit-<timestamp>.json
Multi-URL (recommended):
node scripts/audit-site.mjs \
--urls ./urls.txt \
[--sitemap https://example.com/sitemap.xml] \
[--crawl-from https://example.com/dashboard --depth 2] \
--auth ./auth.json
# writes ./wcag-audit/aggregated.json + per-URL JSON + urls.resolved.txt
5. Run the manual-check pass
Automated axe catches ~30–40% of WCAG failures. For each audited URL, walk
through references/manual-checks.md and run the documented Playwright MCP
procedures. Key manual checks:
- SC 2.4.7 Focus Visible — Tab through the page, verify focus indicator visible at each step
- SC 2.1.2 No Keyboard Trap — verify every widget can be escaped via keyboard
- SC 1.4.10 Reflow — resize to 320×800, confirm no horizontal scrolling
- SC 2.5.8 Target Size (Minimum) — verify interactive targets ≥ 24×24 CSS pixels
- SC 1.3.4 Orientation — resize to portrait and landscape phone dimensions
- SC 3.2.6 Consistent Help (new in 2.2) — help mechanism in same relative position across pages
Use mcp__plugin_playwright_playwright__browser_* tools. Record each manual
finding the same way as axe violations: cite the SC, describe the failure,
propose a fix.
For AAA audits, also consult references/manual-checks-aaa.md.
6. Score and derive conformance
# Multi-URL input
node scripts/score.mjs \
--aggregated ./wcag-audit/aggregated.json \
--out ./wcag-audit/score.json
# Single-URL input is also accepted (wrapped to aggregated shape internally)
node scripts/score.mjs \
--aggregated ./wcag-audit/audit-<timestamp>.json \
--out ./wcag-audit/score.json
Produces per-SC classification (Supports / Partially Supports / Does Not
Support / Not Evaluated). The not-evaluated SCs are those that require
manual-check results — update them based on step 5.
7. Triage & remediate (conversational)
Before starting: announce the total violation count (e.g., "5 violations found; I'll walk through each one.") and keep an internal list of which violations are pending, fixed, or declined as you go.
For each violation (automated or manual):
- Cite the WCAG SC by number and title (look up in
references/wcag-2.2-criteria.md) - Explain user impact plainly (who is affected, how)
- Locate the source: match axe's CSS selector and HTML snippet to files in the user's repo. Use
Grepfor HTML patterns,Globfor component files. - Pick the closest remediation pattern from
references/remediation-patterns.md - Propose a concrete
Edittool diff against the matched source file - For complex cases, explain the fix first, then offer the edit
Handling the user's response (critical):
- Accept / yes / "fix it" → apply the edit, mark this violation fixed, move to the next violation in the list without waiting for further input.
- Decline / no / "skip" / "not now" → do not stop. Mark this violation declined, note any reason the user gave, and move to the next violation in the list immediately. A "no" on one violation is never a signal to end the walkthrough — it only ends this one item.
- "Stop" / "end" / "that's enough" → this is the only input that ends the walkthrough early. Everything else continues.
- Clarifying question → answer it, then re-ask the accept/decline question for the same violation.
Do not batch-ask ("want me to fix all of them?") — present one violation at a time so the user can make per-item decisions.
When the list is exhausted (or the user says "stop"), print a summary: total violations, how many fixed, how many declined (with reasons if given), and remind the user that step 8 (re-audit) is next if any fixes were applied.
This is the high-value step — the report is useful, but engineering teams want diffs.
8. Re-audit after fixes
If any source edits were applied in step 7, rerun the audit from step 4 so
that the findings report and VPAT reflect the post-fix state — a
conformance report based on the pre-fix snapshot is misleading. Overwrite
the prior ./wcag-audit/aggregated.json (or audit-<ts>.json for single-URL)
and rerun score.mjs against the new result.
If no edits were applied in step 7 (report-only run), skip this step.
Diff the new aggregated.json against the prior one to confirm violations
were fixed and none regressed.
9. Generate the findings report
node scripts/report-generate.mjs \
--aggregated ./wcag-audit/aggregated.json \
--score ./wcag-audit/score.json
# writes ./wcag-audit/report.md
Deterministic markdown — same inputs produce byte-identical output. Safe to diff between runs, commit to the repo, or publish. Reflects the most recent audit (post-fix if step 8 ran).
10. Generate the VPAT ACR (mandatory prompt)
You MUST prompt the user via AskUserQuestion whether to generate a
formal Accessibility Conformance Report, even if they did not mention it.
A findings report (step 9) is not the same artifact as a VPAT/ACR — ACRs
are the deliverable procurement and legal teams expect. Never silently
skip this step.
Present three options:
- Skip — the findings report is sufficient for our purposes.
- Worksheet only — markdown table (
./wcag-audit/ACR-<product>-<date>-worksheet.md), no ITI template required. Fast, always works, good for internal review. - Full .docx ACR — fills in a user-supplied ITI VPAT 2.5 INT
.docxtemplate. Best-effort table population; worksheet companion also produced. User must download the template from https://www.itic.org/policy/accessibility/vpat first — this skill does not redistribute it.
If the user picks Worksheet or Full .docx, also collect --product "<name>"
and --version "<v>" via AskUserQuestion before running (both are
required by vpat-fill.mjs).
Commands:
# Worksheet mode
node scripts/vpat-fill.mjs \
--aggregated ./wcag-audit/aggregated.json \
--score ./wcag-audit/score.json \
--product "MyApp" --version "1.0"
# writes ./wcag-audit/ACR-MyApp-<date>-worksheet.md
# .docx mode (user-supplied ITI template)
node scripts/vpat-fill.mjs \
--aggregated ./wcag-audit/aggregated.json \
--score ./wcag-audit/score.json \
--product "MyApp" --version "1.0" \
--template ./VPAT2.5INT.docx
# writes ./wcag-audit/ACR-MyApp-<date>.docx + companion worksheet
After running, report the absolute output path(s) to the user.
Flag reference
audit.mjs / audit-site.mjs
| Flag | Purpose |
|---|---|
--url <url> / --urls <path> / --sitemap <url> / --crawl-from <url> | URL sources (at least one required for audit-site) |
--depth <n> | Crawl depth for --crawl-from (default 1) |
--include <glob> / --exclude <glob> | URL filters (repeatable) |
--auth <path> | Playwright storageState JSON |
--level AA|AAA | WCAG level (default AA) |
--viewport <WxH> | Viewport (repeatable; default: 1280x800 + 375x667) |
--concurrency <n> | Parallel audits (default 1, safe for dev servers) |
--include-best-practice | Include axe best-practice rules (non-normative) |
--out <path> | Output path/directory |
--fail-on <level> | Exit 1 on violations ≥ level; one of: none|minor|moderate|serious|critical|any (default: none) |
Exit codes (all scripts)
0— success, threshold not crossed1— success, threshold crossed (a11y gate failed)2— script error (network, bad URL, expired auth, axe crash)
Reference files
| File | Purpose |
|---|---|
references/wcag-2.2-criteria.md | All 86 SCs: number, level, title, intent, W3C URL |
references/axe-rule-mapping.md | axe rule → WCAG SC mapping (auto-generated from axe-core) |
references/manual-checks.md | Manual-check procedures for A/AA SCs axe can't automate |
references/manual-checks-aaa.md | AAA-only manual checks |
references/remediation-patterns.md | Fix library: per rule, HTML/JSX/ARIA snippets |
references/url-discovery.md | Per-framework recipes to generate urls.txt |
references/section-508-mapping.md | 508 ↔ WCAG crosswalk |
references/en-301-549-mapping.md | EN 301 549 ↔ WCAG crosswalk |
references/vpat-mapping.md | How findings map into VPAT tables |
Known limitations (v0.1.0)
- Auth:
storageStateonly. No bearer tokens, basic auth, or custom headers. - Multi-framework: each audit run covers one framework's recipe at a time.
- 508/EN 301 549: cells derived from WCAG findings via crosswalks, not independently evaluated.
- VPAT
.docxfill-in: best-effort — template layouts vary. The worksheet is always produced as a safety net. - Framework route-manifest parsers: deferred to v0.2.0. Use documented recipes in
url-discovery.mdfor now. - Baseline / suppressions file: not yet supported. Deferred to v0.2.0.