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.
72
91%
Does it follow best practices?
Run evals on this skill
Adds up to 20 points to the overall score
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).