Building AI Tooling that Matters + Fun with Accessibility

Give a model a live URL and ask it to find accessibility problems, and it'll try. It'll look at a screenshot, guess at what seems off, and hand back something plausible-sounding. That's also exactly the wrong way to build this. Accessibility violations aren't a matter of opinion, they're testable against a real spec, and a model eyeballing a screenshot is a worse detector than a piece of software that's been checking these exact rules for a decade. The interesting design question wasn't "how do I get AI to find issues." It was "where does AI actually add something a deterministic tool can't, and where should I keep it out of the way."

What I built

Point it at any live URL and a real Cloudflare Worker spins up a headless browser through Cloudflare's Browser Rendering (Puppeteer under the hood) and runs axe-core, the same accessibility engine behind Chrome DevTools, against the real rendered page. That part is old, boring, and reliable on purpose. What a free scanner won't do is what happens next: instead of handing back a flat list of forty violations, the tool sends them to Claude (Sonnet 5), which groups them by shared root cause (the same broken color token showing up nine times isn't nine bugs, it's one), and drafts an actual ticket for each group, specific fix included, not just a description of what's wrong. Priority ranking isn't the model's call. The whole thing is a static frontend on GitHub Pages talking to that one Worker, with Workers KV handling rate limiting so it stays cheap to run.

The AI's job is exactly two things: deciding which violations actually share a cause, and writing the ticket once they're grouped. Nothing else. The priority score on each ticket, severity weight times how many elements it hits, is arithmetic, computed in code, not something I ask a model to estimate. It's a small design decision that turned out to matter a lot: the parts of this that need judgment get a model, the parts that don't get a formula I can defend.

What actually happened

I ran it against my own site first, mostly to get real test data. It came back with nine separate color-contrast violations, same axe rule, same generic description. A simpler tool would report nine bugs. This one correctly split them into two: seven elements sharing one secondary text color that was failing contrast, and two headings sharing a completely different color with a completely different failure. Same rule ID, two unrelated root causes, and the model told them apart because it was reading the actual failing colors, not just pattern-matching on which rule fired. That's the whole thesis of the project working on the first real run.

The more interesting lessons came from the parts that didn't work the first time. Early on, I caught a bug where bounding boxes were being measured against the full page but the screenshot was only capturing the visible viewport, every overlay box would have pointed at empty space below the fold. And about halfway into scoping this, I almost built something that already exists: a straightforward WCAG scanner with visual overlays. WAVE and axe DevTools already do that, for free, built by people who specialize in exactly that. The actual gap wasn't detection, it was that none of those tools do anything with the results beyond listing them. That reframe is the reason this exists as a triage tool instead of another scanner.

The strangest lesson showed up once the tool went live and I started testing it against sites I don't control. Most sites scan cleanly. One (a major company's marketing site) consistently failed partway through, and figuring out why turned into its own small investigation: was the site's bot protection closing the automated browser session, or was the page itself just heavy enough to run the scanner out of memory? Puppeteer actually gives you a way to tell the two apart, it fires a distinct event specifically when the browser's renderer process crashes, separate from a normal navigation. Wiring that up meant the tool can now tell a visitor which of those two very different things happened, instead of surfacing a raw error and leaving them guessing. A public "scan any URL" tool was never going to work on every URL. The bar that actually matters is failing honestly.

Why this matters beyond the demo

The useful version of AI here wasn't "replace the accessibility checker." It was "sit on top of a real one and do the part that's genuinely a judgment call." That split is a product management skill before it's an engineering one: knowing what to hand a team forty flagged elements to fix one by one, versus what to hand them three grouped root causes with a specific change each. The tool doesn't do accessibility work. It does triage, and triage is what actually clears a backlog.

Check it out at: https://a11y-triage.stephpawlowski.com

Full write-up on Github at https://github.com/stephpawlowski/accessibility-triage-tool