---
description: Run a pa11y accessibility scan on a URL and provide actionable recommendations. Use when asked for "accessibility" scan/report/audit or a full site report.
allowed-tools: Bash, Read, Write, Edit, Glob, Grep, Agent
---

# Accessibility Scan with pa11y

Run an automated accessibility audit using pa11y with dual runners (htmlcs + axe-core) for comprehensive coverage, then provide actionable recommendations grouped by severity. Fix issues if requested.

## Instructions

### Step 1: Determine the target URL

- If the user provided a URL, use it directly.
- If no URL was provided, check if there's a running dev server (look for `vite.config.ts`, `package.json` scripts, or a known project URL).
- If the project has a known production URL (check CLAUDE.md, package.json homepage, or git remote for clues), offer to scan that.
- For static sites, prefer `npm run preview` over `npm run dev` (closer to production).
- Ask the user for a URL only if none can be determined.

### Step 2: Verify pa11y and runners are installed

Run `which pa11y` to check. If not found, install pa11y and the axe runner:

```bash
SUDO_ASKPASS=/tmp/askpass.sh sudo -A PUPPETEER_SKIP_DOWNLOAD=true npm install -g pa11y pa11y-runner-axe
```

If the askpass script doesn't exist, create it first:

```bash
cat > /tmp/askpass.sh << 'SCRIPT'
#!/bin/bash
zenity --password --title="sudo password"
SCRIPT
chmod +x /tmp/askpass.sh
```

Also check if the axe runner is installed:
```bash
npm list -g pa11y-runner-axe 2>/dev/null | grep axe
```
If not found, install it separately:
```bash
SUDO_ASKPASS=/tmp/askpass.sh sudo -A npm install -g pa11y-runner-axe
```

### Step 3: Run the pa11y scan

Always use **both runners** for comprehensive coverage:
- `--runner htmlcs` — pa11y's built-in HTML CodeSniffer engine
- `--runner axe` — the axe-core engine (same engine Lighthouse uses)

```bash
pa11y --runner htmlcs --runner axe --reporter json --standard WCAG2AA --timeout 60000 --wait 2000 "<URL>" 2>/dev/null
```

**Important flags:**
- `--reporter json` — structured output for parsing
- `--standard WCAG2AA` — WCAG 2.1 Level AA (the most common compliance target)
- `--timeout 60000` — 60s timeout for slow pages
- `--wait 2000` — wait 2s after page load for JS rendering
- `--ignore "WCAG2AA.Principle1.Guideline1_4.1_4_3.G18.Fail"` — optionally ignore specific rules if user requests
- For SPAs, add `--wait 5000` to allow more rendering time
- If Chromium isn't found, set `PUPPETEER_EXECUTABLE_PATH` to the system chromium (check `which chromium`)

If the JSON scan succeeds, also run a human-readable summary:

```bash
pa11y --runner htmlcs --runner axe --standard WCAG2AA --timeout 60000 --wait 2000 "<URL>" 2>/dev/null
```

If pa11y exits with code 2 (issues found), that is expected — parse the JSON output.
If pa11y exits with code 1, that is an actual error — report it.

### Step 4: Analyze and present results

Parse the JSON output and organize issues into three categories:

#### Errors (Must Fix)
WCAG violations that are definite accessibility failures. These block users with disabilities.

#### Warnings (Should Fix)
Likely issues that need manual verification but are probably problems.

#### Notices (Consider)
Best-practice suggestions that would improve accessibility but aren't strict violations.

**Deduplication:** Issues reported by both runners (same element + same problem) should be merged into one issue. Note which runner(s) found it.

### Step 5: Present the report

Format the report as:

```
## Accessibility Report: <URL>
**Standard:** WCAG 2.1 Level AA
**Runners:** htmlcs + axe-core
**Total issues:** X errors, Y warnings, Z notices

### Errors (X)

1. **[WCAG Rule ID]** — [Human-readable description]
   - **Runner:** htmlcs / axe / both
   - **Element:** `<selector>`
   - **Context:** `<code snippet>`
   - **Fix:** [Specific actionable fix]

### Warnings (Y)
[Same format]

### Notices (Z)
[Summarize by category rather than listing each one, unless few]
```

### Step 6: Map issues to source code (if in a project)

If running inside a project directory:
- Use Grep/Glob to find the source files that render the flagged elements
- Map each pa11y selector to the actual component/template file and line number
- Include the file path in the recommendation

### Step 7: Fix issues (if requested)

When the user asks to fix the issues:
1. Start with errors (highest priority)
2. Then warnings
3. For each fix, edit the source file directly
4. After all fixes, re-run the pa11y scan to verify improvements
5. Report before/after issue counts

**Common fixes to apply:**
- Missing alt text → add descriptive `alt` attributes
- Missing form labels → add `<label>` elements or `aria-label`
- Low color contrast → adjust colors to meet 4.5:1 ratio (AA)
- Missing document language → add `lang` attribute to `<html>`
- Missing landmark roles → wrap content in `<header>`, `<main>`, `<nav>`, `<footer>`
- Empty links/buttons → add `aria-label` or visible text
- Missing heading hierarchy → fix heading levels (h1 → h2 → h3)
- Missing skip navigation → add skip-to-main link

### Step 8: Generate HTML report (if requested)

For shareable reports:
```bash
pa11y <url> --runner htmlcs --runner axe --reporter html --standard WCAG2AA --timeout 60000 --wait 2000 > accessibility-report.html
```
Save to project root or a location the user specifies.

## Example scan commands

```bash
# Structured JSON output for parsing (default)
pa11y http://localhost:3000/ \
  --runner htmlcs --runner axe --reporter json --standard WCAG2AA --timeout 60000 --wait 2000 2>/dev/null

# Shareable HTML report
pa11y http://localhost:3000/ \
  --runner htmlcs --runner axe --reporter html --standard WCAG2AA --timeout 60000 --wait 2000 > accessibility-report.html

# With chromium path fallback
PUPPETEER_EXECUTABLE_PATH=/usr/bin/chromium pa11y http://localhost:3000/ \
  --runner htmlcs --runner axe --reporter json --standard WCAG2AA --timeout 60000 --wait 2000 2>/dev/null
```

## Notes

- pa11y uses Puppeteer (headless Chrome) under the hood
- Exit code 2 means "issues found" (not an error), parse stdout as JSON
- For React/client-rendered content, add `--wait 2000` or more to let hydration complete
- Always stop any dev server you started when done scanning
- The `axe` runner may find issues `htmlcs` misses and vice versa — always run both
- For stricter compliance, use `--standard WCAG2AAA` (Level AAA)
- For multiple pages, run scans in parallel where possible
- `--screen-capture ./tmp/a11y-screenshot.png` to capture a screenshot for visual review
