npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

a11y-evidence

v0.1.1

Published

Turn your accessibility test results into legal evidence — EAA Annex V, EN 301 549, CI baseline

Readme

a11y-evidence

Turn your accessibility test results into legal evidence.

axe-core runs about 69 million times a week. Detecting accessibility defects is a solved problem. It produces JSON that says "43 violations, rule color-contrast, selector .btn-primary".

That is not what an auditor, a lawyer or a public buyer asks for. They ask:

  • since when do you test, and how often?
  • what have you fixed, when, and by whom?
  • what are you choosing not to fix, and why?
  • how do you stop a regression reaching production?

No npm package makes that translation. a11y-evidence does.

npx a11y-evidence init
npx a11y-evidence scan
npx a11y-evidence lock --update
npx a11y-evidence report --oral

This tool produces documentation from your own test results. It does not certify compliance with any law or standard, and it is not legal advice.


The document you need is probably not the one you found

This is the mistake this package exists to stop.

| | Annex V information | Public sector statement | |---|---|---| | Law | Directive (EU) 2019/882 — the European Accessibility Act | Directive (EU) 2016/2102 | | Applies to | services on the market, including private ones | public sector bodies' websites and apps | | Model | none prescribed; content requirements only | prescribed model, Commission Implementing Decision (EU) 2018/1523 | | Where it lives | in the general terms or an equivalent document | published on the site | | Feedback mechanism | not required | required | | Link to the enforcement body | not required | required | | Written and oral form | required | not required |

The template that is easy to find online is the public sector one. Publishing it for a private service does not satisfy Annex V — and it commits you to a feedback and enforcement procedure that does not apply to you.

So a11y-evidence report generates the Annex V information, and it will refuse to generate the public sector statement unless you tell it you are a public sector body:

$ npx a11y-evidence report --public-sector
a11y-evidence: refusing to generate a public sector accessibility statement.

The public sector statement follows the model in Commission Implementing
Decision (EU) 2018/1523, under Directive (EU) 2016/2102. It applies to public
sector bodies, and it commits you to a feedback mechanism and to a national
enforcement procedure.

For a service on the market, the document you need is the Annex V accessibility
information under Directive (EU) 2019/882 — which is what `a11y-evidence report`
generates by default. Publishing the public sector model instead does not
satisfy Annex V.

The three commands

scan

Orchestrates the engines you already use and normalises what they emit. It does not reimplement any analysis — it is a layer above axe-core, pa11y and Lighthouse.

Runs are written to .a11y/runs/<timestamp>-<sha>.json in a format that does not depend on the engine, and they are meant to be committed. The history is the evidence.

lock

The core of the product: a baseline in .a11y/baseline.json, versioned in your repository, where every known violation is qualified by a person:

{
  "id": "3c1334f1d668",
  "ruleId": "color-contrast",
  "url": "https://example.com/pricing",
  "normalisedSelector": ".pricing-card > p",
  "wcagCriteria": ["1.4.3"],
  "en301549Clauses": ["9.1.4.3"],
  "status": "waiver",
  "justification": "Third-party pricing widget; vendor ticket ACME-4471",
  "owner": "design-system",
  "dueDate": "2026-06-30",
  "firstSeen": "2026-01-05T09:00:00.000Z",
  "lastSeen": "2026-02-10T09:00:00.000Z"
}

In CI, the exit code is non-zero only if a violation is new, a waiver has passed its due date, or a waiver has no justification. Never for the existing stock. That is the condition for adoption: no team accepts a tool that breaks the build over four hundred inherited errors on the day they install it, and a tool that gets switched off produces no evidence at all.

Real output, adopting an existing codebase and then catching a regression:

$ a11y-evidence lock --update
a11y-evidence lock
  6 open, 0 waived, 0 resolved
  6 new, 0 newly resolved

  baseline updated .a11y/baseline.json
  6 finding(s) adopted into the baseline. Qualify each one
  with a status, an owner and a due date before it counts as evidence.

  No new violations and no expired waivers.
$ echo $?
0

# ... someone ships a button with no accessible name ...

$ a11y-evidence lock
a11y-evidence lock
  7 open, 0 waived, 0 resolved
  1 new, 0 newly resolved

  new-violation  New violation: button-name at #checkout on https://example.test/

  Existing findings never fail the build. Only a new violation, an expired
  waiver, or a waiver with no justification does.
$ echo $?
1

Entry ids survive the churn of a real codebase. The selector is reduced to what identifies the element on purpose — ids, data-testid, roles, element names — and everything cosmetic is dropped, so a CSS-in-JS class that changes on every build (css-1a2b3c → css-9z8y7x) does not make an entry look new.

report

Writes four artifacts into .a11y/reports/:

  1. annex-v.md / annex-v.html — the text to put in your general terms, structured requirement by requirement and driven by the real state of the baseline. It will not write "we are committed to"; every line is a fact with a date, and there is a test that enforces that.
  2. en301549-report.html — the criterion-by-criterion table in the VPAT vocabulary (Supports / Partially supports / Does not support / Not evaluated), with the baseline entries that justify every answer that is not "Supports". This is the document a buyer asks for.
  3. remediation-log.md — the history: when you scanned, what appeared, what was fixed, mean and median time to fix. This is the proof over time that Annex V asks for, and it is the one thing a snapshot can never give you.
  4. annex-v-oral.txt with --oral — the spoken script. Annex V explicitly requires the information in written and oral form, and no template on the market ships it. Short sentences, no tables, and the abbreviations a speech synthesiser mangles are spelled out:
There are 55 requirements at level A and level double A. 18 are met.
0 are partly met. 5 are not met. 32 have not been checked automatically,
and need a person to assess them.

The HTML reports are themselves WCAG 2.2 AA: a language on the root, one h1, a skip link, landmarks, table captions and scoped headers, a visible focus indicator, and status never signalled by colour alone. It would be absurd otherwise, and it is the first thing a prospect will check.


"Not evaluated" is a real answer

Automated testing detects a minority of WCAG failures. Of the 55 Level A and AA criteria in WCAG 2.2, the axe rule set covers 23.

A tool that tests 23 criteria and prints "Supports" for the other 32 is not producing evidence, it is producing a misrepresentation. So this one says so:

  32 of 55 criteria are reported as "Not evaluated":
  no automated rule covers them. They need manual assessment before any
  conformance claim covers them.

You can record a manual assessment for any criterion, and it appears in the report with your remarks. What you cannot do is have the tool claim support for something nobody checked — and recorded findings always outrank a manual claim of support.


The mapping table

src/mapping/ holds the correspondence from test rule to WCAG 2.2 success criterion to EN 301 549 clause. It is the part of this project that matters most, so none of it is written from memory:

  • Rule → criterion is generated from axe-core's own rule metadata, which tags every rule with the criteria it tests and, since 4.7, with the EN 301 549 clauses directly. npm run generate:mapping regenerates it; test/mapping.test.ts fails if it drifts from the installed axe-core.
  • Criterion → clause is derived, not transcribed. Clause 9.x.y.z of EN 301 549 is WCAG success criterion x.y.z (EN 301 549 v3.2.1, clause 9 and Table A.1), so a second table that could drift from the first is not kept.
  • pa11y codes carry the criterion in their own structure (WCAG2AA.Principle1.Guideline1_1.1_1_1.H37), so they are parsed rather than looked up — which means pa11y rules this tool has never seen still map.
$ a11y-evidence coverage
Mapping coverage (axe-core)
  105 rules known
  70 map to a success criterion
  30 are best-practice advice, not conformance failures
  5 unmapped
  23 distinct criteria covered

  unmapped: color-contrast-enhanced, duplicate-id, duplicate-id-active,
            identical-links-same-purpose, meta-refresh-no-exceptions

Nothing is silently dropped. A rule that maps nowhere is reported as unmapped, and every exclusion carries its reason — those five are AAA criteria, or 4.1.1 Parsing, which WCAG 2.2 removed. Best-practice rules are kept out of the baseline entirely: failing a build over advice would be wrong.


Configuration

npx a11y-evidence init scaffolds .a11y/config.json with comments:

{
  "targets": [
    "http://localhost:3000/",
    { "url": "http://localhost:3000/account", "name": "Account", "state": "authenticated" }
  ],
  "engine": "axe",                                  // or "pa11y", "lighthouse"
  "viewport": { "width": 1280, "height": 800 },
  "setup": "./a11y-setup.mjs",                      // optional: sign in first
  "directory": ".a11y",
  "service": {
    "name": "Example Service",
    "provider": "Example Ltd",
    "contact": "[email protected]",
    "publicSector": false
  }
}

playwright, pa11y and lighthouse are optional peer dependencies — you install the one you use, and a missing one produces a sentence telling you what to install rather than a stack trace.

CI

A composite GitHub Action ships in action.yml:

- uses: Jonathanlight/a11y-evidence@v0
  with:
    command: all
    update-baseline: ${{ github.ref == 'refs/heads/main' }}
    oral: 'true'

GitLab CI: see .gitlab-ci.example.yml. Gate merge requests against the committed baseline; let only the default branch move it.

What this does not do

  • It does not fix accessibility, and it does not suggest fixes anywhere.
  • It issues no certification of any kind.
  • It invents no WCAG or EN correspondence: every mapping cites its source.
  • There is no SaaS, no hosted dashboard, no account, and no telemetry. Everything is a file in your repository — which is also what makes it usable inside an enterprise.

Licence

MIT. See NOTICE for the sources every mapping is derived from.