Overview
pa11y is a Node-based accessibility scanner that runs WCAG 2.1 AA checks against any URL. This guide installs it, writes a project config, reads the output, fixes the five most common violations, and adds pa11y-ci to a GitHub Actions job. For semantic HTML patterns that prevent violations before they happen, see html-aria-patterns.
Prerequisites
- Node 22 installed. Check with
node -v. - A site running locally or accessible at a public URL. pa11y fetches the page through a headless Chromium browser.
npmin the projectpackage.jsondevDependencies, or installed globally.
Steps
1. Install pa11y and pa11y-ci
npm install --save-dev pa11y pa11y-ciFor a quick one-off scan without a project install:
npx pa11y https://example.com2. Write .pa11yci.json
Place .pa11yci.json at the project root:
{
"defaults": {
"standard": "WCAG2AA",
"timeout": 10000,
"wait": 500,
"reporters": ["cli", "json"],
"chromeLaunchConfig": {
"args": ["--no-sandbox", "--disable-setuid-sandbox"]
}
},
"urls": [
"http://localhost:8080",
"http://localhost:8080/about",
"http://localhost:8080/blog"
]
}wait: 500 gives React and other client-rendered frameworks 500 ms to hydrate before the scan. Increase to 1000 for heavy SPAs. --no-sandbox is required in Docker and most CI environments.
3. Run a scan
Start your dev server, then run:
npx pa11y-ci --config .pa11yci.jsonExample output:
Running Pa11y on 3 URLs:
> http://localhost:8080 - 0 errors
> http://localhost:8080/about - 3 errors
> http://localhost:8080/blog - 1 error
4 errors found across 2 URLs
Exit code 1 means violations were found. Exit code 0 means clean. CI jobs gate on the exit code.
To see the full violation details:
npx pa11y http://localhost:8080/about --standard WCAG2AA4. Read the output
Each violation reports:
Error: Image element does not have an alt attribute (WCAG 1.1.1 A)
Element: <img src="hero.jpg">
Context: <img src="hero.jpg">
Rule: image-alt
Selector: img[src="hero.jpg"]
The Selector field lets you jump directly to the element in DevTools.
5. Fix common issues
Missing alt text (WCAG 1.1.1)
<!-- Before -->
<img src="hero.jpg">
<!-- After: descriptive alt for informative images -->
<img src="hero.jpg" alt="Developer typing on a laptop in a coffee shop">
<!-- After: empty alt for decorative images -->
<img src="divider.png" alt="">Missing form labels (WCAG 1.3.1)
<label for="email">Email</label>
<input id="email" type="email">Low color contrast (WCAG 1.4.3)
Use a contrast checker (browser DevTools or axe DevTools extension) to find passing foreground/background pairs. WCAG AA requires 4.5:1 for normal text, 3:1 for large text.
Missing landmark regions (WCAG 1.3.6)
Wrap page sections in <main>, <nav>, <header>, <footer>. See html-semantic-elements.
Missing <html lang> (WCAG 3.1.1)
<html lang="en">See html-aria-patterns for the full ARIA pattern reference.
6. Integrate with GitHub Actions CI
# .github/workflows/a11y.yml
name: Accessibility
on: [push, pull_request]
jobs:
pa11y:
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
- name: Serve and scan
run: |
npx serve public -l 8080 &
sleep 3
npx pa11y-ci --config .pa11yci.jsonThe sleep 3 gives the static server time to bind. For a dev server that takes longer, use wait-on:
npx wait-on http://localhost:8080 && npx pa11y-ci --config .pa11yci.jsonVerify it worked
# 1. pa11y is installed.
npx pa11y --version
# 2. A known-good page returns exit 0.
npx pa11y https://example.com --standard WCAG2AA; echo "Exit: $?"
# 3. pa11y-ci config is valid JSON.
node -e "require('./.pa11yci.json'); console.log('Valid JSON')"
# 4. CI job passes on the current branch.
# Push to GitHub and check the Actions tab.Common errors
Error: Failed to launch the browser process. Missing--no-sandboxflag inchromeLaunchConfig. Add the two args to the defaults block.Timeout exceeded. The page takes too long to load. Increasetimeoutto 30000 ms or check that the dev server is actually running.0 URLs specified. Theurlsarray in.pa11yci.jsonis empty or the config path is wrong. Pass--configexplicitly.- False positives on dynamically injected content. pa11y scans the DOM at page-load time. Increase
waitto give the framework time to render. pa11y-cinot found. Runnpm cifirst, or prefix withnpx.