Web Accessibility Audit Tools: axe, WAVE, Lighthouse, Manual
axe, WAVE, and Lighthouse catch maybe 30% of real accessibility issues. Here's what each tool actually does, where it fails, and why manual testing still matters.
Why Automated Accessibility Tools Only Tell Half the Story
Honestly, most teams ship an axe scan, see zero violations, and call it done. That's a problem. Studies consistently show automated tools catch somewhere between 20% and 40% of real WCAG 2.2 issues — the rest require human judgment, keyboard testing, and an actual screen reader.
This doesn't mean the tools are useless. axe-core, WAVE, and Lighthouse each serve a specific purpose. Used together, they form a solid baseline that'll surface the obvious problems before you even open a browser extension. The issue is treating them as a finish line rather than a starting point.
The goal of this article is to give you a clear-eyed breakdown of what each tool does well, where it falls short, and how to combine them into a workflow that actually finds accessibility bugs before your users do.
axe-core and the axe DevTools Browser Extension
axe-core is the engine underneath a huge chunk of accessibility tooling — Lighthouse uses it, Playwright's accessibility checks use it, and the standalone axe DevTools browser extension runs it directly in Chrome and Firefox. The library itself is open source (MIT licensed), and version 4.9.x is the current stable release as of late 2026.
What axe does well: it's extremely low on false positives. The team behind it made a deliberate engineering decision to only flag issues it's confident about. That means you can trust every violation it reports. It checks color contrast, missing alt text, form label associations, ARIA role correctness, keyboard focus order problems, and about 80 other rules.
The browser extension is the quickest way to run it manually. Open DevTools, navigate to the 'axe DevTools' panel, hit Analyze, and you get a categorized list of violations with element references and remediation guidance. The free tier covers the core ruleset. Paid tiers add guided manual tests and integrations with Jira.
Where axe misses: it can't detect that your modal traps focus incorrectly in a specific interaction flow. It won't know that your custom dropdown announces the wrong role because the state only changes after a 200ms animation delay. Dynamic content issues, improper reading order in complex layouts, and cognitive accessibility problems are largely invisible to it.
WAVE: Visual Feedback for Designers and Reviewers
WAVE (Web Accessibility Evaluation Tool) from WebAIM takes a different approach. Instead of a list of violations, it injects visual icons and overlays directly onto the page. Red icons mark errors. Yellow icons flag alerts (things that might be problems). Green icons show structural elements like headings and landmarks. It makes the accessibility tree almost tangible.
This visual approach makes WAVE particularly useful for designers and non-developers reviewing pages. A stakeholder can open the WAVE browser extension and immediately see that there are 3 contrast errors and 2 missing form labels without reading any code. It's also useful during design review — you can catch heading hierarchy issues or missing landmark regions before a component is ever built.
WAVE is free, runs entirely client-side (no data sent to servers), and works as both a browser extension and a web-based tool at wave.webaim.org. One thing to watch: WAVE sometimes generates more noise than axe. It flags a lot of alerts that need human interpretation. That's not a bug — it's a different philosophy. But it means you'll spend more time triaging results.
Lighthouse Accessibility Scores: What the Number Actually Means
Lighthouse is baked into Chrome DevTools and runs a weighted audit that produces a score from 0–100. The accessibility section uses axe-core rules plus some of Google's own checks. Getting a 100 doesn't mean your site is accessible. It means it passed the automated rules Lighthouse checks. Those are two very different things.
The score is useful for tracking regressions over time in CI. If your score drops from 94 to 78 between PRs, something broke. That's valuable. But a 100 score on a page with no keyboard-navigable interactive elements, missing skip links, and poorly structured heading hierarchy is entirely possible.
Run Lighthouse from the command line with npx lighthouse https://yoursite.com --only-categories=accessibility --output json to get machine-readable output for CI pipelines. Integrate it with GitHub Actions or your deployment workflow to block merges on score drops below a threshold you define — something like 90 is a reasonable floor for most projects.
One specific Lighthouse check worth calling out: it validates that [id] attributes are unique on the page. This sounds trivial but it's a surprisingly common bug in component libraries where items get rendered in lists and ids get duplicated. If you're building UI components and working with things like custom theme toggles in React, make sure any generated ids include a unique suffix.
Setting Up axe in Your Test Suite with Jest and Playwright
Automated accessibility testing in your CI pipeline is where you stop relying on developers to remember to run browser extensions. The jest-axe package wraps axe-core for Jest, and @axe-core/playwright covers Playwright. Both are worth adding.
Here's a minimal Playwright accessibility test that checks an entire page for violations:
import { test, expect } from '@playwright/test';
import AxeBuilder from '@axe-core/playwright';
test('homepage has no detectable accessibility violations', async ({ page }) => {
await page.goto('http://localhost:3000');
const accessibilityScanResults = await new AxeBuilder({ page })
.withTags(['wcag2a', 'wcag2aa', 'wcag21a', 'wcag21aa'])
.analyze();
expect(accessibilityScanResults.violations).toEqual([]);
});
test('modal dialog traps focus correctly', async ({ page }) => {
await page.goto('http://localhost:3000/components/modal');
await page.click('[data-testid="open-modal"]');
// Verify focus moved into modal
const focusedElement = await page.evaluate(() => document.activeElement?.tagName);
expect(['BUTTON', 'INPUT', 'A', 'DIALOG']).toContain(focusedElement);
// Verify focus is contained — Tab from last element goes back to first
const modalFocusable = page.locator('[role="dialog"] :focus-visible');
await page.keyboard.press('Tab');
await expect(modalFocusable).toBeFocused();
});The .withTags() call is important. By default axe runs all rules. Scoping to WCAG 2.1 AA tags keeps the report focused on what's actually required by most accessibility standards and legal frameworks. You can add 'best-practice' tags too, though that'll surface more opinions than requirements.
Manual Testing: Keyboard Navigation and Screen Readers
Can you tab through every interactive element on the page? Does the focus indicator disappear at any point? These questions sound simple. Answering them accurately requires actually using a keyboard. No automated tool checks that your custom select component built from <div> elements is reachable via Tab, that it responds to arrow keys, and that pressing Escape closes it and returns focus to the trigger.
Keyboard testing takes maybe 10–15 minutes for a page. Start at the top of the page. Tab through every interactive element. Check that: focus is always visible (a visible outline with at least 3:1 contrast ratio against the adjacent color), the tab order matches the visual order, interactive elements respond to Enter and Space appropriately, and modals and drawers trap focus while open.
Screen reader testing is where things get serious. NVDA (free, Windows) and VoiceOver (built into macOS/iOS) are the two you need. JAWS is the market leader for enterprise users but it's expensive. The key things to verify: form fields announce their labels, error messages are announced when they appear, dynamic content updates (like loading states or toast notifications) are announced via aria-live regions, and images have meaningful alt text or are marked as decorative with alt="".
If you're building visually complex UI — think glassmorphism cards or layered gradient components like those covered in the gradient generator guide — screen reader testing is especially important. Visual complexity doesn't translate to the accessibility tree at all. What looks elaborate to a sighted user might be a completely flat, confusing experience for someone using NVDA.
Color Contrast: Tools, Ratios, and Common Failures
WCAG 2.1 AA requires a 4.5:1 contrast ratio for normal text and 3:1 for large text (18pt or 14pt bold). WCAG 2.2 didn't change these thresholds. The forthcoming WCAG 3.0 will use a different model (APCA) but that's not a requirement yet, so don't let anyone tell you to switch.
The fastest tool for checking contrast is the built-in Chrome DevTools color picker — hover any element, open the CSS panel, click the color swatch, and it shows the contrast ratio live. For batch checking, the Colour Contrast Analyser app from TPGi (free, Windows/Mac) lets you pick any two colors on screen. For design files, both Figma and Sketch have contrast plugins.
Common failures in React component libraries: placeholder text on inputs (often rgba(0,0,0,0.4) on white, which is about 2.6:1 — fails), disabled buttons that use opacity: 0.4 (fails, though disabled elements are technically exempt from contrast requirements in WCAG), and icon-only buttons where the icon is a low-contrast SVG with no text alternative. If you're using box shadows to create depth, watch out for text sitting over layered shadow effects — what looks high-contrast to a designer with a calibrated monitor often fails automated checks.
Don't forget focus indicators. A 2px outline with outline-color: rgba(255,255,255,0.15) on a white background is invisible. The WCAG 2.2 Success Criterion 2.4.11 (Focus Appearance, AA) requires the focus indicator to have at least a 3:1 contrast ratio against adjacent colors and cover an area of at least the component's perimeter times 2px.
Building an Accessibility Audit Workflow That Actually Ships
What does a realistic workflow look like? Here's what works without becoming a burden. First, add @axe-core/playwright to your test suite and run it in CI. This catches regressions automatically. Set a threshold — any new violations on wcag2aa rules block the PR. That's your safety net.
Second, use the axe DevTools browser extension during development whenever you're building a new component. Run it before you open a PR, not after review. Five minutes of checking saves a longer conversation in code review. WAVE is good to open once you have a designed page — it gives a quick visual sanity check on structure and labels.
Third, dedicate one manual keyboard test pass per feature area per sprint. It doesn't have to be exhaustive every time. Tab through the primary user flow. Does it work? Can you submit forms, open modals, navigate lists, and get back to the main content? If yes, you're in reasonable shape. Save deep screen reader testing for new complex components and major feature releases.
The tools we've covered — axe, WAVE, Lighthouse — are starting points. Used in combination with keyboard testing and occasional screen reader checks, you'll catch the vast majority of issues before they reach users. And isn't catching bugs before users find them the whole point? That's the workflow. It's not glamorous, but it works.
FAQ
No. A score of 100 means your page passed all of Lighthouse's automated checks, which cover a subset of WCAG rules. Issues like incorrect focus management, poor screen reader announcements, and keyboard traps in custom components won't be caught. Automated tools typically detect 20–40% of real accessibility issues.
axe-core is the open-source JavaScript library (MIT licensed) that powers the accessibility rule engine. The axe DevTools browser extension is a UI wrapper around axe-core that runs in Chrome and Firefox DevTools. The free extension uses the open-source ruleset. Paid axe DevTools tiers add guided manual test workflows and integrations.
Use ['wcag2a', 'wcag2aa', 'wcag21a', 'wcag21aa'] for the standard WCAG 2.1 AA coverage that most legal requirements reference. You can optionally add 'best-practice' but expect more noise that requires manual triage. Avoid running all rules without scoping — it produces too many opinionated results to action efficiently.
4.5:1 for normal text (under 18pt or 14pt bold). Large text (18pt regular or 14pt bold and above) requires 3:1. These thresholds are unchanged in WCAG 2.2. WCAG 3.0 will use the APCA model but is not yet a requirement, so stick with the 4.5:1 and 3:1 ratios for compliance.
The WAVE browser extension processes everything locally in your browser — no page content is sent to WebAIM's servers. The web tool at wave.webaim.org does send the URL and fetches the page on their servers, so don't use the web tool for pages behind authentication or on private networks. Use the browser extension for those.
Manual keyboard test: open the modal, then press Tab repeatedly. Focus should cycle only through elements inside the modal. Pressing Shift+Tab from the first focusable element should wrap to the last. Pressing Escape should close the modal and return focus to the element that triggered it. No automated tool reliably catches all focus trap failures — this one needs manual verification.