Use whenever pages or components need automated accessibility scanning: axe-core via axe.run(), @axe-core/playwright or the CLI on every PR, rule configuration and CI-gateable results, plus pa11y, Lighthouse CI accessibility, WAVE and IBM Equal Access (Section 508) in its references.
The canonical home for this skill is testland/axe-a11y
axe-core is "an accessibility testing engine for websites and other HTML-based user interfaces" maintained by Deque (axe-core). It identifies approximately 57% of WCAG issues automatically and flags items requiring human review as "incomplete" (axe-core).
The integration shape: load the engine into a page (via test
fixture, browser extension, or framework adapter), call
axe.run(), and parse the violations[] array.
a11y-violation-gate
for the ratchet pattern over baseline.If axe-core's direct integration doesn't fit, route to the other scanners below.
axe-core is the primary engine; four alternatives live in references/, each with verified install / CLI / API / CI patterns:
| Scanner | Reach for it when | Reference |
|---|---|---|
| pa11y (htmlcs + axe engines) | Scriptable CLI scans without a test framework; multi-URL batching via pa11y-ci. | references/pa11y.md |
| Lighthouse a11y | The project already runs Lighthouse CI and wants a11y in the same pipeline. | references/lighthouse-a11y.md |
| WAVE | WebAIM-branded reports, visual overlay for designers, or third-party-site audits. | references/wave.md |
| IBM Equal Access | US Section 508 procurement or IBM-branded enterprise compliance. | references/ibm-equal-access.md |
All five emit JSON that a11y-violation-gate normalizes and ratchets.
npm install --save-dev axe-core(Per axe-core.)
For framework integration (preferred over raw axe.run()):
| Package | When to use |
|---|---|
@axe-core/playwright | Playwright tests. |
@axe-core/react | React-component scans during rendering. |
@axe-core/cli | Headless CLI for arbitrary URLs. |
axe-core/api/install (raw) | Custom integrations. |
axe.run() APIimport axe from 'axe-core';
axe.run()
.then(results => {
if (results.violations.length) {
console.error('a11y violations:', results.violations);
}
})
.catch(err => {
console.error('axe error:', err.message);
});(Adapted from axe-core.)
For test environments where loading axe via <script> is easier:
<script src="node_modules/axe-core/axe.min.js"></script>import { test, expect } from '@playwright/test';
import AxeBuilder from '@axe-core/playwright';
test('checkout page passes axe scan', async ({ page }) => {
await page.goto('/checkout');
const accessibilityScanResults = await new AxeBuilder({ page })
.withTags(['wcag2a', 'wcag2aa', 'wcag22aa']) // limit to AA
.analyze();
expect(accessibilityScanResults.violations).toEqual([]);
});// cypress/support/commands.js
import 'cypress-axe';
// In a test
cy.visit('/checkout');
cy.injectAxe();
cy.checkA11y();axe-core plus the framework wrapper (@axe-core/playwright
for Playwright; cypress-axe for Cypress)..withTags([...]) (e.g. wcag2a,
wcag2aa, wcag22aa), then .analyze()..disableRules([...])
or .exclude(selector), each with an inline reason.accessibilityScanResults JSON as a CI artifact.a11y-violation-gate for the ratchet gate instead
of asserting violations.length === 0.incomplete items to periodic manual screen-reader review
(per screen-reader-test-author).axe.run() resolves to violations / incomplete / passes /
inapplicable arrays; each violation carries id, impact, tags,
and a nodes[] array of failing selectors. Rules are selected by WCAG
tag (withTags) and suppressed by rule id or selector (disableRules
/ exclude). Full field tables, tag sets, and jq triage:
references/results-and-config.md.
A Playwright suite adds checkout.a11y.spec.ts. The test navigates to
/checkout, runs
new AxeBuilder({ page }).withTags(['wcag2a','wcag2aa','wcag22aa']).analyze(),
and writes the result JSON to axe-results.json.
The run returns one entry in violations[]: id: color-contrast,
impact: serious, tags including wcag2aa, and one node targeting
button.primary with a failureSummary.
jq -r '.violations[] | "\(.impact): \(.id)"' axe-results.json prints
serious: color-contrast. Piping the JSON to a11y-violation-gate
compares it against the baseline: because this rule/selector pair is
new, the gate fails the PR with the failing selector attributed. Fixing
the button's foreground colour clears it, and the next run reports
violations: [].
# .github/workflows/a11y.yml
name: a11y
on:
pull_request:
push:
branches: [main]
jobs:
axe:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v5
- uses: actions/setup-node@v4
with:
node-version: '20'
- run: npm ci
- run: npx playwright install --with-deps
- name: Run a11y tests
run: npx playwright test tests/a11y/
- name: Upload report
if: always()
uses: actions/upload-artifact@v4
with:
name: a11y-results
path: playwright-report/
retention-days: 14For richer reporting, persist the raw accessibilityScanResults
JSON as a build artifact and pipe to
a11y-violation-gate.
| Anti-pattern | Why it fails | Fix |
|---|---|---|
Asserting violations.length === 0 | Existing legacy debt blocks every PR. | Use the ratchet pattern via a11y-violation-gate. |
| Disabling rules without comment in code | Reviewer can't tell which rules are intentionally off vs. forgotten. | Inline comment explaining why; quarterly review. |
| Scanning only the homepage | Most a11y bugs hide in less-traveled flows. | Scan a representative URL set: home + 1 logged-in dashboard + 1 form-heavy + 1 long-content. |
| Running axe in production | Performance overhead; possible info leak via verbose error logging. | CI / staging only. |
Treating incomplete as pass | Items needing human review go unreviewed; defects escape. | Track incomplete separately; manual review per quarter. |
| One mega-test that runs axe across every page | One failure = whole test fails; remediation hard. | One axe test per page; failure attribution clear. |
screen-reader-test-author).disableRules /
exclude are escape hatches with documented rationale.axe.run().axe.run(), results
structure.a11y-violation-gate -
CI gate using axe results.testland/qa
aa16752
Canonical home
since Jul 23, 2026
If you maintain this skill, you can claim it as your own. Once claimed, you can manage eval scenarios, bundle related skills, attach documentation or rules, and ensure cross-agent compatibility.