Accessibility & visual regression testing

Test what your users
actually see and hear.

One walk through your design system produces four kinds of evidence: what real screen readers announce, what every interaction and focus state looks like, the markup that caused it, and a spec-cited audit. A dropped aria-label or a deleted focus ring shows up in a pull request the way a broken test does: red, quoted, and blocking.

audit 2d3ec8f · main

6/8 passes

Now auditing Select · Structure & text alternatives

Confirming decorative images are hidden from AT (1.1.1)

Deterministic sweep complete: 0 mechanical findings secured before any model pass — 0 tokens.

✓ Alert✓ Button✓ CheckboxSelect
Built for teams with obligations under
  • WCAG 2.2
  • Section 508
  • EN 301 549
  • ADA
  • WAI-ARIA

Every design system claims to be accessible. Almost none can prove it.

We paid for a manual audit six months ago. It was out of date the week after it landed.

Our accessibility expert can't review every PR.

A scanner tells me about missing alt text. It can't tell me our select component stopped announcing its options.

Legal asked for evidence. We have a PDF from last year.

Nobody tests what a screen reader actually announces, on every commit, before the regression ships. Coverli does.

Regression testing

Every commit is heard
before it ships.

We boot the same assistive technology your users run, capture every spoken announcement as text, and diff it against your approved baseline.

  1. 01

    Commit

    A GitHub App builds your Storybook. Change detection fingerprints every component — unchanged stories are skipped and free.

  2. 02

    Real AT speaks

    Real NVDA on real Windows and real VoiceOver on real Macs walk every story: a full read-through, then an interaction pass.

  3. 03

    Diff

    Announcements are captured at the speech-synthesizer layer and normalized, so a baseline only changes when the spoken experience changes.

  4. 04

    Review and approve

    Transcript diff, component screenshot, and the HTML diff that caused it — effect and cause on one screen. Approvals are recorded.

  5. 05

    Merge with evidence

    The regression shows up in the pull request the way a broken test does: red, quoted, and blocking.

transcript diff · Select.stories.jsx

NVDA 2025.1 · Windows 11

approved baseline

  • button, "Filter results", collapsed
  • listbox, "Filter results", 6 items
  • option, "Newest first", 1 of 6, selected

this commit

  • button, collapsed
  • listbox, 6 items
  • option, "Newest first", 1 of 6, selected

the cause

- <button aria-expanded="false" aria-label="Filter results">
+ <button aria-expanded="false">

The spoken experience changed: the trigger no longer announces what it filters. Blocked in review until a human approves the new baseline.

state filmstrip · Select.stories.jsx

4 frames captured
Newest first
restingunchanged

Select, closed

Newest first
focusedchanged

Trigger holds keyboard focus

Newest first
  • Newest first
  • Oldest first
expandedunchanged

Listbox open, option 1 active

Newest first
  • Newest first
  • Oldest first
selectednew

Option committed

No interaction test code. The engine already opens, focuses and toggles every component to test what it announces — the shutter simply opens at the peak of each state.

focus capture · Button.stories.jsx

WCAG 2.4.7
approved baseline
Save changes
this commit
Save changes
- :focus-visible { outline: 2px solid; }
+ *:focus { outline: none; }

The focus indicator is gone. Invisible to your users' eyes, invisible to a resting-state screenshot — captured here because we photograph the element while it holds focus.

Visual testing

And every state your users actually see.

Because we already open, focus and toggle every component to test what it announces, we can photograph it there too — the select open, the dialog visible, the checkbox checked, the focus ring present. No interaction test code to write.

Capture is deterministic — frozen clock, parked animations, pinned rendering — so unchanged code produces byte-identical images. No thresholds, no flake triage. And it is included with the pipeline you already pay for.

The audit

An expert audit of your entire design system. In an afternoon. With receipts.

Every finding cites the spec, quotes your code at file-and-line, and shows its work. The report headline is a verdict, not a gameable score.

SHIP WITH GAPS — any open blocker → DO NOT SHIP · else any serious → SHIP WITH GAPS · else GOOD. Conformance evidence, not a conformance claim; advisory items never gate.

Summary

test-evidence informed

2

findings

0

blocker

1

serious

1

moderate

0

minor

0

deterministic (0 tokens)

Coverage: 4 components · 15 stories · component source only · lenses: Structure & text alternatives, Keyboard, focus & dynamic behavior (2 of 4)

Partial audit — findings and the ship-status verdict reflect only the coverage above, not the whole project.

seriousWCAG 4.1.3 (A)test-corroboratedcomponent

Alert ./src/Alert.stories.jsx

The live-region container and its message text are mounted into the DOM in a single operation, rather than the container persisting and its text being swapped afterward — the documented-unreliable pattern for role="status".

dynamic-context · interaction · src/Alert.jsx:6–14

quoted verbatim

<div className={`demo-alert ${tone}`} role={assertive ? "alert" : "status"}>
  <div>
    {title && <span className="title">{title}</span>}
    {children}
  </div>
</div>

approved test evidence

components-alert--success approved computed tree: "- status: Saved Your application was submitted." — this reflects the tree at initial render, not confirmation that a dynamically inserted status node is reliably announced.

Who it harms
Screen-reader users who trigger the action may never hear the confirmation. NVDA and VoiceOver only reliably announce changes inside a live region that already existed when the change occurred.
The fix
Render the live-region container on mount with an empty text node, and update only its text content when the message changes.
How to verify
Re-run the interaction pass for components-alert--success and confirm the transcript contains the confirmation text after activation, not only at initial read.
audit trail (5 steps)
  • · opened src/Alert.jsx
  • · opened src/Alert.stories.jsx
  • · followed import ./tokens.css
  • · searched corpus: "role=status live region insertion timing"
  • · cited WCAG 2.2 · 4.1.3 Status Messages (Level A)

Evidence, not a score.

Coverli produces conformance evidence, not a conformance claim — the legal determination is always human. That's not a disclaimer; it's why auditors trust our reports.