TL;DR
- Lighthouse runs a subset of axe-core rules and folds them into a weighted number. 100 means "the subset found nothing", not "accessible".
- The full A/AA ruleset is a few lines of Playwright away, and its
incompletebucket is where the interesting findings hide. - On EU sites your consent banner sabotages the audit in both directions. Dismiss it, but audit it once on purpose.
- A second run in mobile emulation catches a whole class of barriers no desktop audit ever sees.
Last year I built a free accessibility checker for Austrian websites (https://webgaudi.at/barrierefreiheit-check/, in German, but WCAG is WCAG, so it works on any public page). A new accessibility law made small businesses here suddenly care, and most of them arrived with the same opening line. "But we have 100 in Lighthouse." Building the checker taught me how little that sentence certifies, and the gap is easiest to explain as four layers you peel off one by one.
Layer 1. What the 100 actually measured
Lighthouse has no accessibility engine of its own. It runs axe-core, the open-source engine behind most a11y tooling, but only a subset of its rules. Rules that need context, produce false positives or resist scoring get skipped, and the rest is folded into one number using per-audit weights. A page can pass every heavy-weighted audit and still ship real barriers.
There is a second ceiling above that one. A large share of WCAG success criteria cannot be verified by any machine. Whether an alt text means anything, whether an error message helps, whether the booking flow survives a screen reader. The W3C itself states that no automated tool can determine conformance. The honest reading of a green 100 is "the automated subset found nothing", which is a fine starting point and a terrible certificate. The inverse is much stronger, by the way. A low score is hard evidence, because everything an automated test finds would also surface in a manual audit.
Layer 2. The rules Lighthouse left on the table
If you are going to trust a number, at least feed it everything axe has for WCAG A and AA. With Playwright that is a few lines.
import AxeBuilder from '@axe-core/playwright'
const results = await new AxeBuilder({ page })
.withTags(['wcag2a', 'wcag2aa', 'wcag21a', 'wcag21aa', 'wcag22aa'])
.analyze()
(Simplified. The production version also scrolls the page to trigger lazy loading and waits for late scripts, because a tracker-injected widget can introduce violations seconds after load.)
Two things change immediately. You see findings Lighthouse never surfaces. And you meet results.incomplete, the bucket of checks axe could not decide, which most tooling silently throws away. Keep it. Text over a hero image is the classic resident. No engine can tell you whether it is readable, but hiding the question is how pages end up "perfect" and unreadable at the same time. The checker shows this bucket as its own "please review manually" category instead of pretending it does not exist.
Layer 3. The banner that audits itself
Here is the failure mode that surprised me most while building the thing. On a typical European site, what you audit on first load is not the page. It is the consent banner sitting on top of it.
The overlay hurts your results twice. Content behind it gets skipped or dumped into incomplete, because axe cannot reliably compute color contrast for covered elements. Meanwhile the banner itself, usually injected by a third-party consent tool, contributes its own violations, so you collect findings for DOM you do not even own while your actual page goes half-untested.
The fix is unglamorous. Dismiss the banner before auditing, using the heuristic pile you would expect, known consent-platform selectors first, then buttons matched by text in German and English. But flip it around once, deliberately. If you always test with the banner closed, nobody ever audits the banner, and consent banners with gray-on-white text or missing keyboard support are everywhere.
Layer 4. The phone your desktop audit never met
A desktop-viewport audit misses an entire class of mobile-only barriers, so the checker always runs a second pass in mobile emulation. Partly that is axe again, because responsive layouts swap menus, collapse columns and reveal different DOM. Partly it is checks no engine does at all, like whether the page overflows horizontally on a narrow screen.
const overflow = await page.evaluate(() =>
document.documentElement.scrollWidth > window.innerWidth)
The two mobile findings I see constantly on real sites are tiny tap targets and pinch-zoom disabled by a user-scalable=no some template shipped years ago. Both are trivial to fix, and both make a site genuinely painful for anyone with imperfect eyesight or motor control. Which, on a phone in sunlight with one free thumb, is everyone.
The checklist version
For your next audit, in order.
- Treat the Lighthouse score as smoke detector, not certificate.
- Run the full axe A/AA ruleset headless, desktop and mobile emulation.
- Dismiss the consent banner first. Audit it separately once.
- Read the
incompletebucket instead of discarding it. - Check horizontal overflow, tap targets and zoom lock on the mobile pass.
- Budget time for the checks no machine can run, keyboard walk, screen reader, reading your own alt texts out loud.
A single green number is a compression of many decisions someone else made about what not to measure. Before you put it in a report, know what got left out.
Top comments (0)