Most accessibility testing tools are either too expensive, too complex, or too shallow. axe-core is excellent but returns 200 results you have to triage. Lighthouse gives you a score but not actionable fixes. WAVE is manual.
We needed something different: a focused, automated WCAG audit that runs on every deploy, checks the things that actually matter, and gives us a clear grade. So we built one. Nine checks. About 200 lines of JavaScript. It runs in Playwright across 5 viewport sizes and catches real issues that expensive tools miss.
Here is everything we learned building it — the checks that matter, the math behind contrast ratios, and the false positives that will waste your time if you do not handle them.
Why We Built Our Own
We run a nonprofit AI education platform with 52 courses and 520+ lessons. Every page needs to be accessible — not as a checkbox, but because our students include people with disabilities, people on budget devices, and people in low-bandwidth areas. They deserve the same quality experience as everyone else.
Commercial tools cost $200-500/month. Open source tools like axe-core are powerful but return overwhelming results. We needed:
- Automated checks on every deploy (no manual clicking)
- A clear grade system (S+ to F) so we know instantly if something broke
- Checks that run at every viewport size (375px to 1440px)
- Zero false positives — every flagged issue should be a real problem
The result: lo-eyes, our open source visual accessibility inspector. It runs 9 WCAG checks across 5 device viewports and produces a grade in under 10 seconds.
The 9 Checks
We started with responsive checks (the things that break on phones) and expanded into WCAG structure and perception checks. Here is what we test and why.
1. Horizontal Overflow
Any element that extends beyond the viewport width on mobile is a critical failure. Users should never need to scroll horizontally to read content. The check walks the DOM tree and skips elements that are properly clipped by scroll containers (like code blocks with overflow-x: auto).
2. Minimum Font Size
Text below 12px is unreadable on mobile devices. We check every leaf text node — paragraphs, spans, links, list items, labels, and buttons. We found 157 lesson files with inline styles using font-size: 0.7rem (11.2px). A batch fix brought everything to 12px minimum.
3. Touch Target Size
Apple's Human Interface Guidelines require 44x44pt minimum touch targets. We check all buttons, inputs, and links with CSS classes (navigation links, CTAs). We skip inline text links in body content (those are fine at line-height) and range inputs (the track is small but the thumb is the actual target).
4. Heading Hierarchy
Screen readers use headings to navigate pages. Skipping from h1 to h3 breaks this navigation. We check that heading levels never skip more than one level. This catches template bugs where developers use headings for visual styling instead of semantic structure.
5. Language Attribute (WCAG 3.1.1)
The <html> element must have a lang attribute. Screen readers use this to select the correct pronunciation rules. Missing it means a screen reader might try to read English content with French pronunciation rules. A single-line check that catches a critical oversight.
6. Landmark Regions (WCAG 1.3.1)
Every page should have a <main> element (or role="main"). Landmarks let screen reader users jump directly to the content they care about. We also check for <nav> and <header> but flag missing <main> as the highest priority.
7. Image Alt Text (WCAG 1.1.1)
Every <img> needs an alt attribute. Decorative images should use alt="" or role="presentation". Our check skips presentation images and flags any img without alt, showing the filename so you can identify which image needs fixing.
8. Accessible Names (WCAG 4.1.2)
Every interactive element — buttons, links, controls — must have a name that screen readers can announce. We check for text content, aria-label, aria-labelledby, title, or an img with alt text inside the element. Icon-only buttons without labels are a common failure.
9. Contrast Ratio (WCAG 1.4.3)
This is the most complex check and the one that taught us the most. WCAG requires 4.5:1 contrast ratio for normal text and 3:1 for large text (18px or 14px bold).
The Contrast Ratio Math
Computing contrast requires three steps: parse colors, calculate relative luminance, and compute the ratio.
Relative luminance uses the sRGB to linear conversion:
function srgbToLinear(c) {
return c <= 0.04045
? c / 12.92
: Math.pow((c + 0.055) / 1.055, 2.4);
}
function luminance(r, g, b) {
return 0.2126 * srgbToLinear(r/255)
+ 0.7152 * srgbToLinear(g/255)
+ 0.0722 * srgbToLinear(b/255);
}
The contrast ratio formula:
ratio = (lighter + 0.05) / (darker + 0.05)
Where lighter and darker are the luminance values of the two colors, ordered by brightness.
The Alpha Compositing Trap
This is where we lost hours. Modern CSS uses semi-transparent backgrounds everywhere: rgba(168, 85, 247, 0.15) for subtle tints, glass effects, card overlays. A naive contrast checker treats these as opaque colors, which produces wildly wrong ratios.
For example, our course filter button had color: rgb(192, 132, 252) on background: rgba(168, 85, 247, 0.15). Naive check: purple on purple = 1.5:1 contrast. Catastrophic failure! But the actual visual appearance was purple text on an almost-black background (the 0.15 alpha means 85% of the dark page shows through). Real contrast: about 7:1. Perfectly fine.
The fix: walk up the DOM tree, collect every background layer, and composite them bottom-up using alpha blending:
effective.r = layer.a * layer.r + (1 - layer.a) * parent.r;
effective.g = layer.a * layer.g + (1 - layer.a) * parent.g;
effective.b = layer.a * layer.b + (1 - layer.a) * parent.b;
This eliminated all our false positives on semi-transparent backgrounds.
The Grade System
Every audit produces a grade based on severity counts:
- S+ — Zero issues. Ship with confidence.
- A — Minor issues only (medium severity). Acceptable.
- B — More than 5 medium issues. Needs attention.
- C — Some high-severity issues. Fix before deploy.
- D — Multiple high-severity issues. Broken accessibility.
- F — 5+ high-severity issues. Do not ship.
We run this across 5 viewport sizes: iPhone SE (375px), iPhone 14 (390px), iPad (768px), laptop (1280px), and desktop (1440px). A page must pass at every viewport to earn S+.
What We Found
Running this audit across our entire site revealed issues we never would have caught manually:
Accessibility Implementation
Need WCAG compliance for your product? Our consulting team builds automated accessibility audit systems and remediation pipelines.
- 157 lesson files with fonts below 12px in inline styles
- Skip link contrast at 4.0:1 (needed 4.5:1) — a one-line CSS fix
- Subscribe button using
purple-400background — not enough contrast with white text - Blog tags using
text-mutedcolor — below contrast minimum - Touch targets on lesson navigation and study mode buttons below 44px
Every one of these was invisible to us during development. We browsed the site daily and never noticed. Automated auditing catches what human eyes miss — especially on viewports you are not testing manually.
Running It
The tool runs as both a CLI and an MCP server (for AI-assisted development). The CLI is the fastest way to audit a page:
# Audit a single page across all viewports
lo-eyes audit /blog/
# Screenshot at a specific device
lo-eyes screenshot /academy/ --device iphone-14
# Full-page scan (chunked for readability)
lo-eyes scan /pricing/ --device iphone-14
It uses Playwright for headless browser rendering, which means it sees the page exactly as a real browser does — including JavaScript-rendered content, computed styles, and responsive breakpoints.
Automating Audits at Scale
Running accessibility checks manually works for a single page. It fails completely for a site with 50 or 500 pages. The solution is integrating your audit tool into your CI/CD pipeline so every deploy gets checked automatically.
We run our audit tool as a post-build step. It crawls every public route, runs all 9 checks, and generates a report. If any page scores below a B grade, the deploy pauses. This catches regressions before they reach users — especially important for dynamic content where a CMS update can break contrast ratios without anyone noticing.
The key insight is that automated audits catch different issues than manual testing. Automated tools excel at catching color contrast failures, missing alt text, and broken ARIA attributes. They miss context-dependent issues like whether an image alt text is actually meaningful, or whether a form's tab order makes logical sense. The best approach combines automated checks in CI with periodic manual audits using assistive technology. Our guide to AI assistive technology covers why this matters beyond compliance.
Beyond WCAG: Real-World Accessibility
WCAG compliance is the floor, not the ceiling. A site can pass every WCAG check and still be painful to use for people with disabilities. Touch targets that technically meet the 44px minimum but are crammed together. Color schemes that pass contrast ratios but cause eye strain. Animations that are technically dismissible but trigger motion sensitivity before the user can react.
The audit tool we built tracks these real-world patterns alongside WCAG rules. We added checks for touch target spacing (not just size), text readability scores, and animation duration. These are not WCAG requirements, but they reflect how people actually use the web. If you are building an audit tool, start with WCAG compliance and then add checks based on feedback from actual users with disabilities. The spec is a starting point, not the destination.
One practical addition we recommend is a readability score. WCAG does not mandate reading level, but a tool that flags content above a 9th-grade reading level helps writers produce content that works for people with cognitive disabilities, non-native speakers, and anyone reading quickly on a mobile screen. The Flesch-Kincaid formula is straightforward to implement in JavaScript and adds real value beyond what WCAG requires. Pair it with your contrast and structure checks for a holistic accessibility picture that goes beyond checkbox compliance.
Common Audit Tool Mistakes
The most common mistake in building an accessibility audit tool is testing only in ideal conditions. Your tool checks a page at one viewport width, one zoom level, with JavaScript loaded and CSS applied. Real users encounter your site at 200% zoom on a 13-inch laptop, with slow connections that load content progressively, and with browser extensions that modify the DOM. Test at multiple viewport widths, at 200% zoom, and with critical CSS disabled.
Another mistake is treating audit results as binary pass/fail. A site with one AA contrast violation on a decorative element is fundamentally different from a site where every form label fails contrast. Weight your results by severity and frequency. A single missing alt tag on a decorative image is a warning. Missing alt tags on all product images is a critical failure. Your grading system should reflect this difference, and your reports should help developers prioritize the fixes that matter most to actual users.
Finally, do not ignore dynamic content. Single-page applications render content after JavaScript executes, modal dialogs change focus contexts, and AJAX-loaded content may lack proper ARIA live region announcements. Your audit tool needs to handle these patterns, which means waiting for content to render and testing interactive states — not just the initial page load. Tools like Playwright or Puppeteer can automate interactions before running accessibility checks, giving you coverage that static analysis completely misses.
Build Your Own
You do not need our tool to start auditing. The core concept is simple:
- Use Playwright (or Puppeteer) to load your page at multiple viewport sizes
- Inject JavaScript that checks the DOM for accessibility issues
- Aggregate results and assign a grade
- Run it in CI so every deploy is automatically checked
Start with the checks that matter most for your users. If you have a mobile audience, prioritize overflow and touch targets. If you serve screen reader users, prioritize headings and landmarks. Build incrementally — nine checks is better than zero.
Accessibility is not a feature you add at the end. It is architecture you build from the start. And the best way to maintain it is to automate the verification.
lo-eyes is open source at github.com/sophiacave/lo-eyes. MIT license. Use it, fork it, make it better.