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-autofix

v0.1.0

Published

Scans React components with axe-core, generates verified WCAG fix diffs via the Claude API, and re-runs axe-core to confirm each fix before showing it to you.

Readme

a11y-autofix

CI License: MIT

Every accessibility tool that fixes issues automatically is paid — this one is free.

a11y-autofix scans React components with axe-core, generates a proposed fix for each violation via the Claude API, and re-runs axe-core against the patched component before showing it to you. No fix is ever surfaced or applied unless it has been proven to actually resolve the violation — an unresolved fix is shown as unverified, never silently discarded and never auto-applied.

Install

npm install a11y-autofix

Requires Node 18+ and an Anthropic API key (ANTHROPIC_API_KEY in the environment, or an ant auth login profile).

CLI usage

npx a11y-autofix scan <path>

Scans a single component file or every .tsx/.jsx file in a directory, and prints each violation plus its proposed fix as a unified diff:

src/components/Avatar.tsx
  [violation] image-alt (critical): Images must have alternative text
  --- a/src/components/Avatar.tsx
  +++ b/src/components/Avatar.tsx
  @@ -8,1 +8,1 @@
  -<img src={avatarUrl} />
  +<img src={avatarUrl} alt="User avatar" />
  [verified] fix confirmed (pass --write to apply)

1 violation(s) found across 1 file(s): 1 verified, 0 unverified, 0 errored.

Nothing is written to disk in this mode — it's a report. Pass --write to apply verified fixes:

npx a11y-autofix scan src/components --write

Unverified fixes are always printed, with the reason they didn't verify, and are never applied — --write or not. The exit code is non-zero whenever a violation is still unresolved at the end of the run, so scan (with or without --write) works as a CI gate.

npx a11y-autofix scan --help

Programmatic usage

The same pipeline the CLI runs is exposed as scan() — it returns structured results instead of printing them, and never calls process.exit:

import { scan } from 'a11y-autofix';

const result = await scan('src/components/Avatar.tsx', { write: true });

for (const entry of result.violations) {
  if (entry.status === 'errored') {
    console.error(`${entry.violation.id}: ${entry.error}`);
    continue;
  }

  console.log(`${entry.violation.id}: ${entry.status}`);
  console.log(entry.patch.newSnippet);

  if (entry.status === 'unverified') {
    console.log(
      'still flagged:',
      entry.verification.remainingViolations.map((v) => v.id),
    );
  }
}

result.violations is a discriminated union on status:

| status | Fields available | Meaning | | -------------- | -------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------- | | 'verified' | context, patch, verification, applied | The fix resolved the violation with no regressions. applied is true only if write was passed. | | 'unverified' | context, patch, verification, applied (always false) | A fix was generated but axe-core still flags something after applying it — see verification.remainingViolations / newViolations. Never written to disk. | | 'errored' | error | context/generate/verify threw for this violation (e.g. an unfixable JSX node, a Claude API error) — the rest of the scan continues. |

scan() throws only if targetPath doesn't exist; per-violation failures never abort the scan.

The individual pipeline stages (detectViolations, gatherContext, generateFix, verifyFix) are also exported, if you want to drive the pipeline yourself instead of using scan().

How the verify loop works

This is the part that makes the tool trustworthy rather than just plausible. The core risk with any LLM-generated fix is that the model says it fixed something but didn't — so nothing here takes Claude's word for it.

detect/  --violations-->  context/  --FixContext-->  generate/  --Patch-->  verify/  --VerificationResult-->  cli/ / scan()
   ^                                                                            |
   |                                                                            |
   +----------------------- re-run axe-core against the patched component -----+
  1. detect renders your component in a real DOM (jsdom + @testing-library/react) and runs axe-core against it — real accessibility testing, not pattern matching.

  2. context locates the exact JSX element behind a violation by parsing the component's AST — never by string-matching source text — and extracts it, its parent, its siblings, and its prop types, so the model has real surrounding context to work from.

  3. generate sends Claude only that one element and constrains its response, via structured output, to a single field: the replacement JSX. It cannot touch the parent, the siblings, or anything else in the file.

  4. verify is the trust boundary. It applies the proposed patch to the component's source in memory only — the file on disk is never modified at this stage — re-renders the patched component, and re-runs axe-core against it. Two things have to be true for a fix to pass:

    • the original violation is actually gone, and
    • no new violation was introduced by the fix.

    Only a fix that clears both checks is marked verified. Everything else — a fix that didn't fully resolve the issue, or fixed one thing while breaking another — comes back unverified, carrying the exact violations that prove it. It is never discarded, and it is never written to your files, --write or not.

If you run with --write, this is also the only thing that decides what touches disk: verified fixes get applied, unverified ones don't, full stop. The test suite (verify.test.ts, scan.test.ts, cli.test.ts) covers both directions directly — a fix that genuinely resolves a violation, and a fix that changes something irrelevant and is correctly caught and rejected — so this isn't just a design claim, it's asserted behavior you can read and re-run yourself.

See PLAN.md for the full module-by-module design notes, including known limitations (e.g. color-contrast currently can't be detected — jsdom has no real paint implementation for axe-core to measure against) and open decisions.

Contributing

See CONTRIBUTING.md for setup, the development loop, and what to check before opening a PR.

License

MIT