Build-time WCAG 2.2 AA auditor and EU accessibility statement generator for static sites, built for the freelancers and small agencies who have to comply with the European Accessibility Act (in force since 28 June 2025) without an accessibility budget. It started in the DACH region — the BFSG in Germany, the BaFG in Austria — and the statement now names the statute and supervisory body of seven countries: Austria, Germany, Switzerland, Spain, France, Italy and the Netherlands, each in its own language as well as English.
npx eaa-kit audit # WCAG 2.2 AA report; finds your build itself
npx eaa-kit diff a.json b.json # what a change made worse, and what it fixed
npx eaa-kit init # write an eaa.config.json
npx eaa-kit statement # accessibility statement, in one of seven countriesNot legal advice. eaa-kit reports what an automated engine can and cannot determine about your markup. Automated testing catches a minority of accessibility barriers; it is a floor, not a certificate.
pnpm add -D eaa-kit # npm i -D eaa-kitNode 22.22, 24.15 or 26 and newer. jsdom, which the browserless engine is built on, supports only the even-numbered release lines, so the odd-numbered ones (23, 25) are not supported here either.
Audits your build. Globs the HTML out of ./dist, parses it with jsdom and runs
axe-core against it. No Chromium download, fast enough for CI, and it never fetches
anything or executes your site's JavaScript. --browser swaps in real Chromium for the
rules that need layout and CSS; --fast goes the other way and skips the rules the
browserless engine cannot decide anyway, rather than running them and discarding the
answer.
eaa-kit audit ./dist --fail-on seriousSites that render on a server and never write HTML to disk — Next.js without a static export, Nuxt, SvelteKit, anything behind a CMS — are audited running instead:
eaa-kit audit --url http://localhost:3000Writes the statement. A Barrierefreiheitserklärung, a déclaration d'accessibilité, a dichiarazione di accessibilità — whatever the country calls it — from one config file, as Markdown or HTML, naming that country's statute and supervisory body, and optionally listing the barriers a real audit found.
eaa-kit statement --output src/content/a11y.md
eaa-kit statement --country FR --lang fr # or ES, IT, NL, AT, DE, CHEach country's statement is a document under its own law rather than a translation of another's, so where a national regime asks for more than this — France's RGAA declaration, Italy's filing with AgID — the text says so instead of letting a generated file look like it settles the matter.
Fails only on what is new. The first run on a site that already exists finds everything at once. A baseline records what is already wrong so the build fails on regressions instead, without ever letting an accepted barrier look like a passing one.
eaa-kit baseline ./dist
eaa-kit audit ./dist --baseline eaa-baseline.jsonSays what a change did. Two reports, and the difference between them: what is new, what was fixed, and what the later run never looked at — which is kept apart, because a violation missing from a run that stopped early was not fixed by anybody.
eaa-kit diff before.json after.jsonSays what to do about it. Not "images must have alternative text" and a link — who the barrier stops, what to change, and the corrected form of your markup, in your framework's idiom where that differs. Deterministic and offline: no model, no API key, nothing that could invent a fix that looks right and is not.
✗ image-alt critical, WCAG 1.1.1
A screen reader announces this image by its filename, or skips it entirely.
Fix: Add alt text describing what the image conveys…
→ <img src="/assets/logo.svg" alt="What this image shows">
written in src/components/Header.astro:12
Says how much of WCAG it could reach. WCAG 2.2 has 55 success criteria at Levels A and
AA. axe-core has rules touching 23 of them. Every run says so, and --coverage lists all
55 with what this run reached on each — never as a percentage, because most of WCAG cannot
be automated and a score would present that as a fact about your site.
Says what it did not measure. A crawl that stopped at its page limit, or could not fetch forty URLs, no longer produces a report that looks like a complete one.
Reports in four shapes: a console report for whoever ran it, JSON for other tools, SARIF for GitHub code scanning, and a self-contained HTML page for the client whose site it is.
Says it once. The flags a project runs on every build belong in the project, not in the
build script that repeats them. eaa.config — the same file the statement reads — takes an
audit block of defaults, and baseline reads the
keys that mean the same thing to it. A flag you type still wins, so a one-off --browser
check needs no edit to a committed file.
Runs in your build: a Vite plugin covering SvelteKit and Remix too, and integrations for Astro, Nuxt, Eleventy and webpack — or the bundled GitHub Action.
Audits what a CMS serves. WordPress, TYPO3, Craft, Laravel, Symfony, Rails and Django write no HTML to disk, so eaa-kit names the command that gets the site running and audits that instead — following the site's own sitemap, wherever it keeps it.
eaa-kit audit --url http://localhost:8000 --sitemap /sitemap_index.xml| Auditing a build | The audit command, both engines, exit codes, and what an automated run can and cannot tell you |
| Defaults from eaa.config | Writing the flags down once, and what still overrides them |
| The statement command | The config file, the seven countries, and filling a statement from audit results |
| Baselines | Adopting the tool on a site that already has violations |
| Comparing two runs | The diff command, and what it refuses to call fixed |
| Coverage of WCAG | What an automated engine can reach at all, and what it cannot |
| Report formats | The JSON contract, SARIF, and the HTML report |
| Integrations | The build plugins, the GitHub Action, and the two builders that get a command instead |
Complete generated output for every format is checked in under examples/.
The two things worth knowing before you rely on any of it:
Automated testing finds a minority of barriers. It cannot judge whether alternative text is accurate, whether a page makes sense in reading order, or whether a form can actually be completed with a screen reader. A clean report means nothing was found by this engine, which is not the same as a site being accessible — and none of the four output formats will say otherwise on your behalf. This is not a disclaimer the tool leaves you to take on faith: it counts it. Of the 55 WCAG 2.2 A and AA criteria, 34 have no automated rule at all, and every report says so.
The browserless engine cannot decide everything. jsdom has no layout, so rules that depend on rendering — colour contrast, target size, computed overflow — cannot be evaluated. axe-core does not know that and will report some of them as passing. eaa-kit never passes those on: they are reported as not evaluated, with the reason, whatever axe-core said about them. See what the browserless engine can and cannot tell you.
The same applies to the statement: it says what you told it. eaa-kit cannot check whether those claims are true, and a statement claiming full conformance for a site that is not conformant is worse than no statement at all.
See CHANGELOG.md.
MIT
{ "audit": { "dir": "build", "failOn": "critical", "browser": true } }