Automated accessibility scanning across the five engines - axe-core (primary), pa11y, Lighthouse a11y, WAVE, and IBM Equal Access. Authors and runs axe-core scans via the `axe.run()` JavaScript API or the @axe-core/playwright / @axe-core/cli wrappers, parses `violations[]` into per-rule severity, configures rule disable / disable-by-tag patterns, and emits CI-gateable output; references/ carry the pa11y CLI (htmlcs + axe runners), Lighthouse CI `categories:accessibility` assertions, the WAVE API / overlay, and IBM Equal Access (Section 508) with their verified CLI / API / config. Use for any automated a11y scanner setup - axe-core for JS/TS UI test suites on every PR, and the references for CLI-only, Lighthouse-pipeline, WebAIM-branded, or Section 508 scanning.
75
94%
Does it follow best practices?
Run evals on this skill
Adds up to 20 points to the overall score
View guide
Passed
No findings from the security scan
Companion reference for axe-a11y. Consult when the project already runs
Lighthouse CI for Web Vitals and wants a11y coverage in the same pipeline
instead of a separate scanner. If the project doesn't already use Lighthouse
CI, prefer direct axe integration (SKILL.md) - Lighthouse adds a layer.
Lighthouse CI ships five audit categories: Performance, Accessibility, Best Practices, SEO, and Progressive Web App. The Accessibility category runs a curated subset of axe-core rules (lhci). It audits whole pages (scored 0 - 1, rule pass rate weighted by severity); for component-level coverage, use axe in unit / integration tests.
npm install --save-dev @lhci/cliAdd a11y assertions to the same .lighthouserc.js used for perf, so one
config drives both categories:
// .lighthouserc.js
module.exports = {
ci: {
collect: {
url: ['http://localhost:3000/', 'http://localhost:3000/checkout'],
numberOfRuns: 3,
startServerCommand: 'npm run start',
},
assert: {
assertions: {
// Accessibility - category score (0-1)
'categories:accessibility': ['error', { minScore: 0.95 }],
// Per-audit overrides - error on critical-impact a11y rules
'aria-required-attr': ['error', { minScore: 1 }],
'button-name': ['error', { minScore: 1 }],
'label': ['error', { minScore: 1 }],
'meta-viewport': ['error', { minScore: 1 }],
// Lower-impact rules - warn but don't block
'color-contrast': ['warn', { minScore: 1 }],
'image-alt': ['warn', { minScore: 1 }],
},
},
upload: { target: 'temporary-public-storage' },
},
};Per lhci, assertion levels are 'error' (CI fails), 'warn'
(surfaced but doesn't fail), and 'off'. Run all three phases (collect /
assert / upload) with npx lhci autorun.
assert.assertions applies one threshold set to every collected URL. When
pages need different bars, use assertMatrix: an array pairing a
matchingUrlPattern regex with its own assertions block (lhci).
assertMatrix and assertions are mutually exclusive at the assert level,
and the first matching pattern wins - order specific patterns before the
catch-all:
assert: {
assertMatrix: [
{ matchingUrlPattern: '.*/checkout.*',
assertions: { 'categories:accessibility': ['error', { minScore: 0.98 }] } },
{ matchingUrlPattern: '.*',
assertions: { 'categories:accessibility': ['error', { minScore: 0.90 }] } },
],
},Used in assertions:; per lhci (full list in Lighthouse's
accessibility audit documentation):
| Audit ID | What it checks |
|---|---|
aria-allowed-attr | ARIA attributes are valid for the element's role. |
aria-required-attr | Required ARIA attributes for the role are present. |
aria-roles | Valid ARIA roles only. |
aria-valid-attr-value | ARIA attribute values are valid. |
button-name | Buttons have accessible names. |
bypass | Skip-link or landmark for bypassing repeated content. |
color-contrast | Foreground / background contrast >= 4.5:1 (3:1 large). |
document-title | <title> is set. |
frame-title | <iframe> has a title attribute. |
html-has-lang | <html> has lang. |
image-alt | <img> has alt. |
label | Form fields have associated labels. |
link-name | Links have accessible names. |
meta-viewport | <meta name="viewport"> doesn't disable zoom. |
tabindex | No tabindex > 0. |
# .github/workflows/lighthouse.yml
jobs:
lighthouse:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v6
- uses: actions/setup-node@v4
with: { node-version: '20', cache: 'npm' }
- run: npm ci
- run: npm run build
- run: npx lhci autorun
env:
LHCI_GITHUB_APP_TOKEN: ${{ secrets.LHCI_GITHUB_APP_TOKEN }}
- if: always()
uses: actions/upload-artifact@v4
with: { name: lighthouse-reports, path: .lighthouseci/ }minScore: 1 on the category blocks every PR on a single
moderate-severity failure; start at 0.95 and tighten.screen-reader-test-author) and direct axe scans.lhci autorun, config shape,
assertion levels, assertMatrix).