Lighthouse CI Accessibility Budgets
Permalink to "Lighthouse CI Accessibility Budgets"Lighthouse’s accessibility category runs a subset of axe-core audits and produces a weighted score from 0 to 100. Lighthouse CI (@lhci/cli) runs it on every build and can fail CI when the score or specific audits regress. It is easy to adopt and gives product owners a number they understand. It is also easy to misuse: a score of 100 means the page passed the audits Lighthouse runs, on the state it loaded — not that it conforms to WCAG — and a score threshold alone lets individual regressions through if other audits improve.
This page configures Lighthouse CI as a regression budget for data pages, with assertions per audit. It belongs to accessibility testing recipes.
Spec reference
Permalink to "Spec reference"Lighthouse CI configuration (lighthouserc.js):
ci.collect.url— URLs to audit;ci.collect.puppeteerScriptcan log in or set up state;numberOfRunsfor stability.ci.assert.assertions— per-category (categories:accessibility) and per-audit (button-name,color-contrast,td-headers-attr,aria-required-children, …) assertions with levelserror/warnand options likeminScore.ci.upload— to temporary public storage, an LHCI server, or the filesystem.
Lighthouse’s accessibility audits are axe-core rules; their weights determine the score. Audits that do not apply to a page are excluded from the score, so a page with no tables cannot fail table audits.
When Lighthouse CI fits — and when it does not
Permalink to "When Lighthouse CI fits — and when it does not"It fits as a broad, cheap regression net across many pages, especially if Lighthouse CI already runs for performance. The same run yields accessibility results at no extra cost.
It does not fit as the primary accessibility test for data UIs. It audits the page as loaded (unless scripted), runs fewer rules than a full axe scan with WCAG tags, and cannot see keyboard behaviour or announcements. Use it alongside axe scans of grid states with Playwright and keyboard tests.
The misapplication to name is reporting “Lighthouse accessibility: 100” as evidence of WCAG conformance in procurement or compliance documents. It is evidence that a set of automated checks passed on specific pages.
Annotated code example
Permalink to "Annotated code example"// lighthouserc.js
module.exports = {
ci: {
collect: {
url: [
'http://localhost:8080/invoices',
'http://localhost:8080/invoices?sort=amount&dir=asc', // a state reachable by URL
'http://localhost:8080/reports/revenue',
],
numberOfRuns: 1, // accessibility is deterministic
settings: { onlyCategories: ['accessibility'] }, // fast, if perf runs elsewhere
},
assert: {
assertions: {
// Trend floor: current level, raised as you fix things
'categories:accessibility': ['error', { minScore: 0.95 }],
// Hard gates on the audits that matter for data UIs
'button-name': 'error',
'link-name': 'error',
'color-contrast': 'error',
'td-headers-attr': 'error',
'th-has-data-cells': 'error',
'aria-required-children': 'error',
'aria-required-parent': 'error',
'aria-allowed-attr': 'error',
'duplicate-id-aria': 'error',
'label': 'error',
'heading-order': 'warn',
},
},
upload: { target: 'filesystem', outputDir: './lhci-reports' },
},
};
# CI step
- run: npm run build && npx http-server dist -p 8080 &
- run: npx wait-on http://localhost:8080
- run: npx @lhci/cli autorun
- uses: actions/upload-artifact@v4
with: { name: lighthouse, path: lhci-reports }
URL-reachable states (a sort in the query string, a filter parameter) are the cheapest way to extend Lighthouse beyond the initial load. For states that need interaction, puppeteerScript can perform it, but at that point a Playwright test with axe is usually simpler.
Keyboard & AT behaviour
Permalink to "Keyboard & AT behaviour"Lighthouse includes a short list of “manual” items in its report (logical tab order, focus visibility, custom controls) that it does not test. Treat them as a reminder list, not as checked items.
| Lighthouse report item | Tested automatically? | Where to test it |
|---|---|---|
| Buttons have names | ✓ | — |
| Table headers valid | ✓ | — |
| Contrast (text) | ✓ | — |
| Tab order is logical | ✗ (manual list) | Keyboard tests, manual audit |
| Focus visible | ✗ (manual list) | Focus screenshot tests |
| Custom controls have roles | ✗ (manual list) | Role queries, screen reader tests |
Integration context
Permalink to "Integration context"Lighthouse and pa11y overlap; pick one as the page-level net rather than running both for the same purpose — see pa11y-ci for multi-state data pages. If Lighthouse CI already runs for performance, the DOM and accessibility-tree budgets from setting a DOM node budget in CI can live in the same configuration.
The rule decisions behind Lighthouse’s audits are axe-core’s; suppressions should follow the ledger in configuring axe-core rules and handling false positives.
Gotchas
Permalink to "Gotchas"Score variance. Accessibility scores are deterministic for the same DOM, but pages with random or time-based content vary. Seed data for CI.
Not-applicable audits. A page whose table fails to render has no table audits, and may score higher. Assert on content presence (a row count) in a separate test.
Authenticated pages. Use puppeteerScript to log in, or run against a seeded, unauthenticated preview build.
Design system notes
Permalink to "Design system notes"Run Lighthouse CI against the design system’s documentation site as a broad smoke check, but rely on per-story axe scans for components. For products, provide a shared lighthouserc preset with the data-UI audit gates above, so every product’s budget starts from the same list.
Testing checklist
Permalink to "Testing checklist"FAQ
Permalink to "FAQ"Does a Lighthouse accessibility score of 100 mean a page is WCAG compliant?
No. It means the page passed the automated audits Lighthouse runs, in the state it was loaded. Keyboard behaviour, announcements, focus and many WCAG criteria are not tested.
Should Lighthouse CI fail on the accessibility score or on individual audits?
On individual audits for the issues that matter, with the category score as a floor that only goes up. A score threshold alone can hide a regression behind an unrelated improvement.
Can Lighthouse test interactive states of a data grid?
Only states reachable by URL or scripted with a Puppeteer script. For interactive states, Playwright or Cypress tests with axe are simpler and more thorough.
Does Lighthouse run the same rules as a full axe-core scan?
No. It runs a curated subset of axe-core audits with its own weighting. A full axe scan with WCAG tags checks more rules, which is why Lighthouse works best as a broad regression net rather than the main accessibility test.
How should the score floor be set initially?
At the current baseline, so nothing gets worse, with critical audits gated immediately. Raise the floor as fixes land.
Related
Permalink to "Related"- axe scans of grid states — deeper per-state checks
- pa11y-ci for multi-state pages — the issue-list alternative
- DOM node budget in CI — the performance budget alongside