An AI that has to prove its accessibility fix, checked by a real browser.

▶️ Watch the demo · 🔗 Try it live · ⚡ Run the 12-second walkthrough · 💻 GitHub

💡 Inspiration

About one in six people lives with a significant disability. For them, a missing image description or a keyboard trap on a checkout page decides whether they buy or leave.

Accessibility checkers give developers a flat list of failures. The list can't say which barrier matters most, which journeys pass through it, or whether a fix worked. AI repair tools make the last part worse. They write plausible fixes and declare success without checking.

I wanted an AI that has to prove its fix.

⚙️ What it does

AccessRipple opens your website in a real browser and maps it. A model proposes a fix and commits in writing to what that fix will do. Then a rescan checks every claim.

  1. Scan. Playwright Chromium crawls up to five same-site pages and runs axe-core on each. It also presses Tab through each page to catch keyboard traps and invisible focus.
  2. Press the buttons, safely. Ordinary controls are activated once each in disposable pages, and what happened is recorded. Only reading requests are sent. Pay, delete, send and sign out are never pressed.
  3. Draw one evidence map. Pages, controls, journeys, browser errors and findings become one graph. Every point is labelled observed, inferred or not tested.
  4. Show the ripple. Select a finding to see the pages one and two recorded actions away, and how many journeys pass through its page.
  5. Put the AI on trial. The model proposes the smallest change, grounded in the exact markup the browser recorded. It then commits in writing: "this finding will be gone, those two will still be there." A human applies the change. The rescan reports each prediction as held, failed or not rechecked, and not rechecked never counts as a pass.
  6. Hand over the result as a .patch, a pull request summary, or a linked Obsidian vault.

On any live site the change is only simulated inside the scanner's own browser. AccessRipple never edits a site it scans.

📊 At a glance

  • 12 seconds for the one-press walkthrough, with no account and no install
  • 3 findings no longer observed after the change on the demo shop
  • 240 tests passing, with strict TypeScript
  • 0 invented elements: a proposed change that doesn't match the recorded markup is dropped

🌊 The idea behind the ripple

A finding's ripple uses only journeys the browser actually took. Take the graph of pages and recorded actions, and let p be the page that holds a finding. The pages at ring k are

$$R_k(p)=\lbrace v \in V \mid d_G(p,v)=k \rbrace, \quad k=1,2$$

where $$d_G$$ counts the recorded actions on the shortest observed path between two pages. Reach is a count, never a score:

$$\mathrm{reach}(p)=\left|\lbrace v \mid d_G(p,v)<\infty \rbrace\right|-1$$

Each AI prediction is checked against the rescan and gets one of three verdicts:

  • Held: the page was rechecked and the claim was true.
  • Failed: the page was rechecked and the claim was false.
  • Not rechecked: the page was not looked at again. This is never promoted to "held".

🛠️ How I built it

  • Front end: a React 19 and TypeScript studio with a spatial canvas map, an inspector, and a proof dialog showing the page before and after the change.
  • Back end: a Fastify API bound to loopback, with a URL safety gate. It blocks private networks, DNS rebinding and cloud credential addresses, and it decodes NAT64 addresses.
  • Scanner: Playwright Chromium with axe-core installed before the site's own scripts, so a strict CSP can't block the checks. A Tab-key probe finds keyboard traps.
  • AI layer: the Vercel AI Gateway, running MiniMax M3.1 Flash. Every sentence must cite an evidence id that exists in the scan, and the markup of a proposed change must match what was recorded. Otherwise it is dropped.
  • Quality: 240 tests with Vitest, strict TypeScript, and Zod schemas at the boundaries.
  • Hosting: the shared demo runs behind Tailscale Funnel.

🧗 Challenges

  • Reasoning models can spend their whole token budget thinking and return nothing. A low reasoning effort and a large token budget fixed that.
  • A public site resolved to a NAT64 address (64:ff9b::/96) and was wrongly blocked as private. The safety check now decodes the IPv4 address inside it.
  • A scan that couldn't run its checks used to look like a finding. It is now a separate "unchecked" state.
  • The rate limit was charged before the address was validated, so a few typos locked a user out. It is now charged only when a scan launches.

📚 What I learned

  • A tool should say what it did not check. Coverage gaps and "not rechecked" matter as much as findings.
  • AI output is only useful if something outside the model can check it.
  • Automated checks catch a useful subset of barriers. They don't certify accessibility, and the studio says so on every result.

🔭 What's next

  • Support for more rules and for logged-in flows.
  • Direct pull request creation from an accepted change.
  • A CI mode that fails a build when a prediction fails.

▶️ Try it

  1. Open the live demo.
  2. Press Run the judge walkthrough.
  3. Watch it scan, propose a fix, record the prediction, apply the change, rescan and open the proof, in about 12 seconds.

The shared demo keeps scan history visible to other visitors, so use public URLs only. The source code is MIT-licensed on GitHub.

Built With

Share this project:

Updates

Submission history