axle · docs
axle docs
Everything you need to install, configure, and operate axle in CI. axle is one engine (axe-core 4.11) shipped through five surfaces — GitHub Action, npm CLI, Netlify / Cloudflare Pages / Vercel plugins, and a WordPress plugin. The configuration shape is identical across surfaces so you only learn it once.
Quick start
Add to .github/workflows/accessibility.yml:
name: Accessibility
on: [pull_request]
jobs:
a11y:
runs-on: ubuntu-24.04
permissions:
contents: read
pull-requests: write
steps:
- uses: actions/checkout@v4
- uses: asafamos/axle-action@v1
with:
url: ${{ secrets.PREVIEW_URL }}
fail-on: seriousThat's it. The Action will scan the URL, post a sticky PR comment with violations grouped by severity, and exit non-zero if any violation crosses your fail-on threshold. AI fixes are off by default — see the AI section below to enable.
GitHub Action
Marketplace listing: axle — a11y / WCAG Accessibility CI. Source: asafamos/axle-action. Pin to @v1 for major-version stability or to a specific tag like @v1.1.0 for exact reproducibility.
Two scan modes
External URL. Pass url. Useful when your PR has a preview URL from Vercel / Netlify / Cloudflare / Render etc.
- uses: asafamos/axle-action@v1
with:
url: https://preview-${{ github.event.pull_request.number }}.example.com
fail-on: seriousBuild-and-serve locally. Leave url empty. The Action runs install-command → build-command → start-command in the background, waits for the port, and scans localhost:PORT.
- uses: asafamos/axle-action@v1
with:
install-command: npm ci
build-command: npm run build
start-command: npm start
wait-on-port: "3000"
fail-on: seriousnpm CLI
Same engine as the Action, runs anywhere Node 20+ runs. Useful for GitLab / Jenkins / CircleCI / Buildkite / Bitbucket / local dev.
# one-shot npx axle-cli scan https://example.com --fail-on serious # with AI fixes (requires ANTHROPIC_API_KEY env) npx axle-cli scan https://example.com \ --fail-on serious \ --with-ai-fixes true \ --max-ai-fixes 10 \ --json-out axle-report.json \ --markdown-out axle-report.md # install globally npm i -g axle-cli axle-cli scan https://example.com
Exit codes: 0 — passing at threshold. 1 — violations at or above threshold. 2 — invalid arguments / fatal error.
Netlify / Cloudflare Pages / Vercel
Each hosting plugin runs the scan against the platform's preview URL on every deploy and fails the deploy if the threshold is crossed.
Netlify
Add to netlify.toml:
[[plugins]] package = "axle-netlify-plugin" [plugins.inputs] fail-on = "serious"
Cloudflare Pages
Add as a Worker bound to onPostDeploy. See the axle-cloudflare-plugin README.
Vercel
Add a build step in vercel.json:
{
"buildCommand": "next build && npx axle-vercel-plugin"
}WordPress plugin
Different model — runs in the WP admin via a hidden iframe + the bundled axe-core build. No outbound calls. Install from the WordPress.org plugin directory (search “axle” or “a11y scanner”) once approved, or upload the ZIP from the source repo.
Configuration reference
All inputs accepted by the Action (CLI uses the same flags with--kebab-case):
| Input | Default | Description |
|---|---|---|
url | "" | External URL to scan. If empty, build + start locally. |
fail-on | serious | critical | serious | moderate | minor | none. Threshold above which the run fails. |
with-ai-fixes | false | Generate Claude fix diffs in the PR comment. Requires anthropic-api-key. |
max-ai-fixes | 10 | Cost guard — caps Claude calls per run. |
anthropic-api-key | — | Anthropic API key. Pass as a repository secret. Required when with-ai-fixes: true. |
anthropic-model | claude-sonnet-4-6 | Override the model used for fix generation. |
github-token | github.token | Used to post the PR comment. Default workflow token works if you set pull-requests: write. |
comment-on-pr | true | Post / update the sticky comment on the triggering PR. |
install-command | npm ci | Used only when url is empty. |
build-command | npm run build | Used only when url is empty. |
start-command | npm start | Used only when url is empty. Backgrounded. |
wait-on-port | 3000 | Port to wait for before scanning localhost:PORT. |
Output format
Two artifacts are emitted on every run: axle-report.json (machine-readable) and axle-report.md (human-readable).
{
"url": "https://example.com",
"scanned_at": "2026-04-27T14:00:00.000Z",
"engine": "axe-core@4.11.3",
"summary": {
"violations": 7,
"critical": 1,
"serious": 3,
"moderate": 2,
"minor": 1,
"passes": 142
},
"violations": [
{
"id": "color-contrast",
"impact": "serious",
"wcag": ["1.4.3"],
"help": "Elements must have sufficient color contrast",
"helpUrl": "https://dequeuniversity.com/rules/axe/4.11/color-contrast",
"nodes": [
{
"html": "<button class='cta'>...</button>",
"target": [".hero > .cta"],
"failureSummary": "Element has insufficient color contrast of 2.85"
}
]
}
]
}The schema is stable across major versions. WCAG mappings come from axe-core's own metadata (we don't re-tag).
AI fixes
With with-ai-fixes: true+ an Anthropic API key, axle sends each violation's offending HTML and the rule metadata to Claude Sonnet, which returns a unified diff suggestion. The diff appears inline in the PR comment. axle never commits anything autonomously — a human reviews and merges (or edits) the suggestion.
Anthropic's API does not train on this data per their enterprise API terms. On the Business plan we add a zero-retention pass-through flag so prompts aren't logged.
The cap is max-ai-fixes (default 10) per run. Hosted AI on Team / Business plans pulls from a monthly budget — see pricing.
Common patterns
Vercel preview URL per PR
- uses: asafamos/axle-action@v1
with:
url: https://${{ github.event.pull_request.head.ref }}-${{ secrets.VERCEL_PROJECT }}.vercel.app
fail-on: seriousMultiple URLs (one job per page)
jobs:
a11y:
strategy:
matrix:
path: [/, /pricing, /signup, /checkout]
runs-on: ubuntu-24.04
steps:
- uses: actions/checkout@v4
- uses: asafamos/axle-action@v1
with:
url: ${{ secrets.PREVIEW_BASE }}${{ matrix.path }}
fail-on: seriousMonorepo (only run when frontend changes)
on:
pull_request:
paths:
- 'apps/web/**'
- 'packages/ui/**'Authenticated routes
For routes that require login, run the Action against a test-user-authenticated preview URL (Playwright fixture) rather than reproducing auth in the Action itself. We're working on a lightweight cookie-pass-through option for v2.
Troubleshooting
The Action runs but fails immediately with a Chromium error
This usually means the Playwright cache wasn't restored. The first run on a runner downloads ~150MB of Chromium; subsequent runs use the cache. Add --with-deps if running on a non-Ubuntu runner — but most issues resolve by waiting through the first cold run.
No PR comment appears
Workflow needs permissions: pull-requests: write. For forked-PR workflows, the default github.token has read-only permissions; use a PAT withpull-requests: write as github-token.
Scan times out / runs too slowly
Cold runs take ~90s including browser install. If your run is materially slower, check wait-on-port— if the app isn't serving on that port, the Action waits until timeout before failing. Verify start-command actually binds to the expected port.
False positives on a known-OK pattern
axe-core rules can be disabled per-violation via data-axe-ignoreattributes — but please don't do this casually. axe-core is calibrated for zero false positives at serious+ severity. If you genuinely have a false positive, file an issue at axe-core upstream.
API key + auth
The Open plan needs no axle API key — the Action and CLI run with no axle credentials. AI fixes use your own Anthropic key directly.
Team and Business plans get an AXLE_API_KEY by email after subscription. Use it as:
- GitHub Action: pass as
axle-api-key: ${{ secrets.AXLE_API_KEY }}. - CLI:
export AXLE_API_KEY=<key>before running. - Web: paste at /account once to unlock unlimited fixes in the browser scan.
Lost the key? It's in the welcome email under subject “Your axle API key”. If you can't find it, email asaf@amoss.co.il with the email used at checkout.