axe-core vs Lighthouse CI for PR Gating

This decision guide is part of Pa11y CI Integration, and it answers a narrow, recurring question: when you can only put one accessibility engine on the blocking path of a pull request, should it be axe-core or Lighthouse CI? Both evaluate a rendered DOM against WCAG-aligned rules, but they differ sharply in how they report, how fast they run, and how much control they give you over the exit code that decides a merge. Picking the wrong one produces either a flaky gate developers learn to bypass or a gate so coarse it misses real regressions.

Baseline factors that decide the choice:

  • Rule coverage and granularity per violation
  • Wall-clock speed on the PR critical path
  • Exit-code and threshold control for a clean pass/fail
  • False-positive profile and how noise erodes trust
axe-core vs Lighthouse CI gating comparison Two columns compare axe-core and Lighthouse CI: axe-core emits per-rule violations and a fast pass/fail; Lighthouse CI emits a weighted score with broader audits but slower runs. axe-core per-rule violations fast pass/fail low false-positive rate exact exit-code control Lighthouse CI weighted score slower, multi-run a11y + perf signals score-threshold gate vs
axe-core gives per-rule violations with exact exit-code control for a fast block; Lighthouse CI gives a weighted accessibility score plus performance signals at the cost of slower runs.

Why the Choice Matters

A PR gate is a trust contract. If it fires false alarms, engineers stop reading it and lobby to make it non-required; if it is too slow, it falls off the critical path and people merge before it finishes. axe-core and Lighthouse CI fail differently on both axes, so the choice is not cosmetic — it determines whether the gate actually holds.

axe-core reports discrete, rule-level violations (each tied to a WCAG criterion such as 1.4.3 or 4.1.2) and returns a clean non-zero exit on any violation, which makes “block this PR on serious and critical issues” trivial to express. Lighthouse CI instead computes a weighted accessibility category score; gating on a score threshold is coarser — a single new violation may not move the aggregate enough to fail, while unrelated audit changes can. Lighthouse also runs the full page and benefits from multiple runs to stabilize, so it is slower. The flip side: Lighthouse surfaces performance and best-practice signals in the same pass, which axe-core does not.

Comparison

Factor axe-core Lighthouse CI
Output unit Per-rule violations, each WCAG-mapped Weighted category score (0–1) + per-audit detail
Granularity for gating High — fail on any serious/critical rule Coarse — score threshold can miss single violations
Speed on PR path Fast — single render, single pass Slower — full audit, multi-run for stability
Exit-code control Exact — non-zero on violation, scriptable by severity Score-based via assert thresholds in lighthouserc.js
False-positive profile Low; conservative, well-targeted rules Low–moderate; score can drift with browser/render
Scope beyond a11y Accessibility only Accessibility + performance + best practices + SEO
Best gate role Primary blocking check on every PR Secondary score guard on key pages
WCAG coverage style Discrete criteria (e.g. SC 1.3.1, 1.4.3, 4.1.2) Audit subset rolled into a category score

For most teams the cleanest split is to make axe-core the primary blocking check — fast, granular, and easy to gate by severity — and run Lighthouse CI as a secondary, often non-blocking, score guard on a handful of representative pages where the performance and best-practice signals add value. This keeps the blocking path quick and noise-free while still tracking the aggregate accessibility score over time.

# axe-core blocks; Lighthouse CI reports a score without blocking the merge.
jobs:
  axe-gate:            # primary, blocking
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - uses: actions/setup-node@v4
        with: { node-version: '20', cache: 'npm' }
      - run: npm ci && npm run build
      - run: npx serve dist --listen 3000 &
      - run: npx axe http://localhost:3000 --exit --tags wcag2aa  # exit 1 blocks
  lighthouse-score:    # secondary, non-blocking signal
    runs-on: ubuntu-latest
    continue-on-error: true   # records the score but does not block the merge
    steps:
      - uses: actions/checkout@v4
      - uses: actions/setup-node@v4
        with: { node-version: '20', cache: 'npm' }
      - run: npm ci
      - run: npx lhci autorun   # asserts categories:accessibility minScore

If your gating policy needs branch-protection wiring and admin-bypass rules, align this split with Pull Request Gating & Branch Policies. To run both engines across many routes under one threshold, Pa11y CI integration wraps the axe engine for batch sweeps.

Edge Cases & Conditional Guards

  • Score plateaus hide regressions: A Lighthouse accessibility score can stay flat while a new violation is introduced. Pin individual audits (color-contrast, label) to error if you gate on Lighthouse, or keep axe-core as the hard block.
  • Single-page applications: Both engines need a stable, hydrated DOM. axe-core via a runner lets you waitForLoadState('networkidle'); Lighthouse benefits from multiple runs to absorb timing noise.
  • Auth-gated routes: axe-core through Playwright can carry session state; Lighthouse needs a seeded preview or a login step before the audit.

Pipeline Impact

The two engines produce different artifacts: axe-core emits structured per-violation JSON ideal for PR annotation, while Lighthouse CI emits a category score plus an HTML report ideal for trend dashboards. Treat the axe-core exit code as the merge-blocking signal and the Lighthouse score as a tracked metric. Combining them with Progressive Threshold Management lets the blocking axe gate tighten over sprints while the Lighthouse score trends upward as evidence.

Common Pitfalls

  • Gating solely on the Lighthouse score: A coarse threshold lets single violations slip through. Pair it with a per-rule check.
  • Putting Lighthouse on the blocking path everywhere: Its slower, multi-run audit drags the PR critical path. Reserve it for key pages.
  • Ignoring exit codes: Piping || true after axe-core defeats the gate. Let the non-zero exit propagate.
  • Comparing scores across browser versions: An unpinned browser shifts the Lighthouse score independent of code. Pin the browser if you gate on it.

FAQ

Which engine has better WCAG rule coverage for a blocking gate? axe-core exposes more discrete, individually gateable rules mapped to specific WCAG criteria, which makes “block on serious and critical” precise. Lighthouse evaluates an audit subset and folds it into a single weighted score, so it is better as a tracked signal than as the fine-grained blocker.

Can I just use Lighthouse CI for everything? You can, but the score-threshold model makes it easy to miss a single new violation that does not move the aggregate, and its multi-run audit is slower on the PR path. Most teams keep axe-core as the hard block and use Lighthouse as a secondary score guard.

Do these tools double-count the same violations? They overlap on common checks like color contrast (WCAG 2.2 SC 1.4.3) but express them differently — axe-core as a discrete rule, Lighthouse as a contributing audit. Run axe-core as the authority for pass/fail and treat the Lighthouse audit as corroborating signal rather than a second vote.