Lighthouse scores 100: Why axe can still find issues
Lighthouse can score a page 100 for accessibility while axe reports issues on that page. Different rule selections, versions or page states can explain the mismatch. The score alone proves neither accessibility nor WCAG conformance. W3C explains why human evaluation is also needed.
For this article, we reproduced the difference on a demo page. Lighthouse scored 100 while a direct axe run using the same engine version reported six failing rules. After we corrected the examples, axe no longer reported those failures.
What does the Lighthouse accessibility score measure?
The score combines selected automated audits into a weighted average. Each scored audit passes or fails. One unnamed button therefore fails the whole button-name audit, even if every other button has a name. More affected buttons do not increase the deduction for that one audit. See Chrome's explanation of accessibility scoring.
Scored audits include button names, form labels, text contrast, valid ARIA attributes and the page language. Their weights differ: Lighthouse 13.5.0 gives button-name a weight of 10 and color-contrast a weight of 7. A higher weight has more influence on the score.
Check the configuration for the Lighthouse version you use to see the exact selection:
- Manual audits, such as checking logical tab order, have a weight of 0.
- Some automated checks, including
empty-heading, also have a weight of 0. They can appear in the report without reducing the number. - Audits that do not apply are excluded from the average. Having no relevant elements does not add extra passed tests.
Lighthouse rounds the average, so read the individual results even when the score is 100. The number is neither a share of WCAG criteria met nor a count of every barrier.
Why do Lighthouse and axe run different checks?
Lighthouse uses axe-core with its own rule selection. In version 13.5.0, it starts with the wcag2a and wcag2aa tags. Tags identify groups of rules. Lighthouse then explicitly enables or disables individual rules. You can inspect this in the Accessibility gatherer source.
Lighthouse also enables target-size from the WCAG 2.2 rules and autocomplete-valid from WCAG 2.1. These are individual overrides; Lighthouse does not enable the whole wcag22aa tag. The base selection therefore does not limit all checks to WCAG 2.0. Other rules are explicitly disabled.
A separate axe integration may use different tags, excluded regions or disabled rules of its own. “Tested with axe” does not yet describe the scope. The axe API documents these options.
Our test: A score of 100 and six failing rules
We built a product demo for this article with two versions of the same lamp page: deliberate barriers and the corresponding corrections. Open the Rule selection case. All six problems in the table below come from this demo. The page also shows the source it uses.
We tested the linked pages in German and English. Lighthouse 13.5.0 and the direct axe run both used axe-core 4.13.0 in the same Chrome browser at the same desktop viewport size. For axe, we enabled wcag2a, wcag2aa, wcag21a, wcag21aa and wcag22aa. Both scans covered the whole document, including instructions and controls, without exclusions.
Lighthouse scored 100 and reported no failed accessibility audits. axe reported the following six failing rules. A rule can affect several elements; six here counts rules. On narrow screens, scroll the table sideways.
After correction, the same axe configuration reported no violations. This verifies the corrections in the tested state.
Bad and better: Give a graphic a useful name
A screen reader user needs the information in the graphic as text. The bad example provides none:
<div role="img" id="lh-chart"></div>
For a simple graphic with two values, an accessible name can convey the information:
<div role="img" id="lh-chart"
aria-label="Brightness: low setting 300 lumens, high setting 500 lumens."></div>
The corrected test example passed role-img-alt. Its name describes the values shown. A complex chart may need a longer description or data table. A purely decorative graphic does not need an invented name; displaying it without an image role may be more suitable.
Bad and better: Allow readers to adjust text spacing
This inline rule can stop a custom stylesheet from changing letter spacing to make text easier to read:
<p id="lh-spacing" style="letter-spacing: 0 !important">A warm light for reading, writing and the last task of the day.</p>
The corrected example removes that declaration:
<p id="lh-spacing">A warm light for reading, writing and the last task of the day.</p>
It then passed avoid-inline-spacing. Also check whether increased spacing clips content or hides controls. WCAG 1.4.12 requires applicable text-spacing changes to work without loss of content or functionality. Only the spacing properties used by the language and script apply. It does not require every page to use those spacing values by default.
Can Lighthouse audit an open dialog?
Lighthouse can audit the current page state with a snapshot. A normal navigation audit examines the state after loading. A snapshot can capture a dialog opened afterwards. User flows combine these steps. See the Lighthouse user-flow documentation for the supported modes.
The product demo has a separate Dialog state case. The close button is initially not displayed, and the navigation audit scored 100. After we opened the dialog, the snapshot reported button-name and scored 94. In the improved dialog variant, the button has a name and the snapshot scored 100. The 94 belongs to this test setup: audit weights and which other audits apply affect the number.
This tested sequence uses an existing Puppeteer page as page. It opens the dialog demo through #lh-open and audits the visible dialog #lh-dialog:
import { startFlow } from 'lighthouse';
const url = 'https://demo.navable.io/en/examples/v1/lighthouse-axe/barriers/dialog';
await page.setViewport({ width: 1280, height: 900, deviceScaleFactor: 1 });
const flow = await startFlow(page, {
config: {
extends: 'lighthouse:default',
settings: {
onlyCategories: ['accessibility'],
formFactor: 'desktop',
screenEmulation: { disabled: true },
throttlingMethod: 'provided',
disableStorageReset: true,
},
},
});
await flow.navigate(url);
await page.click('#lh-open');
await page.waitForSelector('#lh-dialog');
await flow.snapshot({ name: 'Opened lamp settings' });
const report = await flow.createFlowResult();
axe reported aria-hidden-focus as needing review (incomplete) in both open variants. In our keyboard tests, Tab and Shift+Tab kept focus inside the dialog, and Escape returned it to the opening button. These observations do not replace a screen-reader test.
A snapshot alone does not confirm focus movement or keyboard behavior. Timespan mode has no Accessibility category in this version. Our guide to cookie banners and dialog states covers the additional checks.
Why do we still need keyboard tests?
A scan with no reported issues does not confirm that every action works with a keyboard. The separate Keyboard operation case demonstrates this with the add action: a clickable <div> responds to a pointer but is skipped by Tab. Lighthouse scored 100, and the direct axe run reported no violations.
In the improved keyboard variant, the action uses a native <button type="button">. Tab reaches it, and Enter or Space activates it. WCAG 2.1.1 requires keyboard operation, with an exception for functions that depend on the path of movement.
We therefore test this action manually. axe also includes keyboard-related rules, such as scrollable-region-focusable, but a scan does not identify every keyboard problem.
How to investigate different results on your page
Compare test conditions first. You can then trace a missing finding to a rule selection rather than guessing at the cause.
- Record versions. Note Lighthouse, the browser and axe-core. Lighthouse JSON includes
lighthouseVersionandenvironment.credits["axe-core"]. A separately installed axe extension may use another version. - Open the same content. Match the URL, viewport, language, login and consent choices. Wait for the content you intend to test. A closed dialog and an open dialog are different test cases.
- Compare rules. Record tags, individually enabled rules, overrides and excluded regions. An axe run limited to WCAG 2.1 does not automatically include the WCAG 2.2
target-sizerule, for example. - Separate result types.
violationsare failing rules.incompletemeans further investigation is needed. For scored audits, these undecided results are not scored as confirmed violations. Informative audits can includeincompleteresults among their reported items. An execution error is not a pass either. - Repeat the same test after a change. Compare the affected elements and rules to see whether the correction resolves the specific problem.
You can keep Lighthouse in your workflow and add targeted axe tests for important states. navable Developer Workflows offers a starting point for work in your IDE. Check the tags there too: Local MCP 0.2.0 defaults to WCAG 2.0 and WCAG 2.1 A/AA tags, without wcag22aa. Adding another engine also leaves manual checks to do; see the Pa11y/HTMLCS article.
For help planning the full process, use our guide to testing an accessible website. Choose a concrete task, such as completing a purchase, and test every step through confirmation with a keyboard and screen reader.
