Automated WCAG accessibility auditing for Stellar dApp frontends — as a GitHub Action and as a standalone CLI. It crawls a running app, flags real violations with axe-core, then uses an LLM to explain why each one matters to a real user and to propose an actual code fix as a diff, sourced from your real component files. An opt-in mode can open a PR with the fixes for a maintainer to review.
There are, as of this writing, no accessibility tools anywhere across the ~667 repos in the Drips Wave Stellar Program. Wallet-connect flows, Soroban contract-interaction forms, DEX swap panels, and dashboards across the ecosystem ship without a single automated WCAG check. That's not a niche gap — it's a total blind spot in an ecosystem that otherwise has reasonably mature tooling for testing, fuzzing, and CI.
That matters for a boring, non-ideological reason: inaccessible dApps have a smaller addressable user base than accessible ones. A color-contrast failure on a "Connect Wallet" button, an unlabeled amount input on a swap form, or a transaction-confirmation modal that traps keyboard focus doesn't just fail an audit — it silently turns away screen reader users, keyboard-only users, low-vision users, and switch-device users before they ever get to the part of the product that's supposed to be innovative. For every project building on Stellar, fixing this is not a compliance checkbox; it's more users who can actually complete a swap, a payment, or a contract call.
stellar-a11y-bot is built to close that gap directly, and to do it in a way that's actually useful to a maintainer who has never thought about accessibility before — not just "here's 40 axe-core error codes," but "here's what's broken, here's who it hurts and how, and here's a concrete diff you can review."
- Crawls a local dev server or deployed URL with Playwright, discovering same-origin routes.
- Audits every page with axe-core against WCAG 2.0/2.1 A and AA rules — color contrast, missing ARIA labels, keyboard navigation traps, missing alt text, form label associations, focus order, and more.
- Explains each violation in plain English via an LLM: which real users it affects (screen reader users, keyboard-only users, low-vision users) and what actually breaks for them — not a generic accessibility platitude.
- Proposes a fix: resolves the violation back to a real source file under a configurable source root, fetches its actual contents, and asks the LLM for the smallest correct diff.
- Optionally opens a PR (
--auto-pr) with the higher-confidence fixes applied to a branch, for a maintainer to review and merge — never auto-merged.
Flagging and explaining is the core deliverable here — not auto-fixing. A tool that "fixes" things maintainers can't trust to actually be correct is worse than no tool, because it teaches people to ignore its output. So:
- Explanations and violation detection (axe-core) don't depend on fix generation working, and are the thing this project cares most about getting right.
- Every suggested fix is a diff to review, never a merge.
--auto-propens a PR; nothing is pushed to a default branch, ever. - Fixes carry a confidence level (
high/medium/low) based on how the violation was resolved back to source.--auto-pronly applieshigh-confidence fixes by default. - If the LLM can't confidently identify the offending code in the resolved file, it's instructed to say so explicitly (
NO_CONFIDENT_FIX) rather than guess — and the tool treats that as "no fix," not an error. - No
ANTHROPIC_API_KEY? The audit still runs and still reports every real violation — you just don't get explanations or fixes. A raw, accurate report beats no report.
npm install -g stellar-a11y-bot
# Point it at a running dev server or deployed URL
stellar-a11y-bot audit --url http://localhost:5173
# With explanations + suggested fixes (requires ANTHROPIC_API_KEY)
export ANTHROPIC_API_KEY=sk-ant-...
stellar-a11y-bot audit \
--url http://localhost:5173 \
--source-root ./src \
--explain \
--suggest-fixes
# Opt-in: open a PR with high-confidence fixes
stellar-a11y-bot audit \
--url http://localhost:5173 \
--source-root ./src \
--auto-pr \
--repo yourorg/your-dapp \
--base-branch main
# requires GITHUB_TOKEN in the environmentAs a GitHub Action:
- uses: stellar-a11y-bot/stellar-a11y-bot@v0
with:
url: http://localhost:5173
source-root: ./src
fail-on: serious
anthropic-api-key: ${{ secrets.ANTHROPIC_API_KEY }}See SETUP.md for full setup, local development, and how to run the fixture regression test yourself.
fixtures/sample-dapp is a small static Stellar-DEX-style page ("Lumen Swap") with six intentionally injected WCAG violations — missing alt text, a button with no accessible name, two color-contrast failures (verified below), unlabeled form inputs, and disruptive positive tabindex values. index-fixed.html / app-fixed.js is the corrected reference version — including a real keyboard focus-trap implementation for the transaction-confirmation modal, which the broken version deliberately lacks.
The two injected contrast failures are real, not just plausible-looking colors — computed WCAG contrast ratios:
| Element | Foreground / background | Ratio | WCAG AA minimum | Result |
|---|---|---|---|---|
| Connect Wallet button (broken) | #b7b7c9 / #ffffff |
1.98:1 | 4.5:1 | Fail |
| Rate hint text (broken) | #cfcfe0 / #ffffff |
1.54:1 | 4.5:1 | Fail |
| Same elements (fixed) | #5c5c72 / #ffffff |
6.50:1 | 4.5:1 | Pass |
Below is real output from the tool's normalization, source-resolution, and reporting pipeline — demo/example-console-output.txt and demo/example-report.json were generated by demo/generate-example-report.ts, which feeds axe-core-shaped violation data (matching axe-core's actual rule IDs and help text for each injected issue) through the real flattenToFindings → resolveSource → printReport code path against the actual fixture files on disk:
stellar-a11y-bot — audit of http://localhost:4173/
1 route(s) crawled · 8 finding(s)
4 critical 4 serious
[CRITICAL] image-alt — Missing alt text
route: /
Images must have alternate text
[CRITICAL] button-name — Missing/invalid ARIA
route: /
Buttons must have discernible text
source candidate found (high) but no fix generated
[SERIOUS] color-contrast — Color contrast
route: /
Elements must meet minimum color contrast ratio thresholds
source candidate found (high) but no fix generated
...
(Note on how this was produced: the sandbox this project was built in couldn't reach Playwright's browser-download CDN, so the live crawl step (src/crawler/crawl.ts) wasn't exercised end-to-end there. Everything downstream of the crawl — flattening, source resolution, categorization, diff generation, report formatting — is real, tested code, run against the real fixture. CI (.github/workflows/ci.yml) runs the full pipeline including the live browser crawl on every push, and is the actual regression test; see SETUP.md for how to run that same live crawl locally.)
With ANTHROPIC_API_KEY set, --suggest-fixes turns each high-confidence finding into a diff like this one (illustrative — modeled on the real prompt/output format in src/fix/generate.ts, for the color-contrast violation on the Connect Wallet button):
Index: styles.css
--- styles.css before
+++ styles.css after
@@ -30,7 +30,7 @@
.connect-wallet-btn {
background: #f4f4fa;
- color: #b7b7c9;
+ color: #5c5c72;
border: none;
border-radius: 20px;And --auto-pr opens a PR whose description looks like:
a11y: 3 automated accessibility fix(es) from stellar-a11y-bot
This PR was opened automatically after an audit of
https://your-dapp.example. It applies 3 high-confidence fix(es). Please review each diff carefully before merging.
- color-contrast in
src/components/WalletButton.tsx(high confidence)
- Screen reader users aren't affected by this one, but low-vision users relying on browser zoom or the page's natural contrast — rather than a screen reader — may not be able to read the "Connect Wallet" label at all against this background.
- label in
src/components/SwapForm.tsx(high confidence)
- A screen reader user tabbing into this amount field hears only "edit text, blank" — no indication it's the "From" amount — because the placeholder text isn't a substitute for a real
<label>.
crawler/ Playwright BFS crawl + axe-core scan per page
audit/ flatten violations -> per-node findings, categorize, resolve
back to source files, format reports
llm/ Anthropic API client + plain-English explanation prompts
fix/ fetches real source file, generates a unified diff fix
pr/ opt-in: applies high-confidence fixes to a branch, opens a PR
cli/ commander-based CLI entry point
Source resolution (audit/resolve-source.ts) is a heuristic, not a source-map resolver: it matches a violation's data-testid/id attribute or distinctive text content back to a file under --source-root via grep, falling back to a component-name guess. Every match carries a confidence level, and fix generation and auto-PR both respect it.
- Static/SPA crawling via same-origin
<a href>discovery — client-side routers that don't render real anchors may need--extra-routeto reach specific screens (e.g. behind a wallet-connect step). - Source resolution is heuristic and works best when your markup has stable
data-testid/idattributes — see the issues below for planned improvements. - Fix generation targets HTML/JSX/Vue/Svelte template code; it isn't scoped to complex state-management refactors.
- Tested against a static-HTML fixture and against React/TS-shaped Soroban dApp patterns; broader framework coverage is tracked in the issues below.
See the open issues for scoped next steps, or SETUP.md to get a dev environment running.
MIT — see LICENSE.