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

@surea11y/cli

v1.0.0

Published

Command-line WCAG 2.2 accessibility scanner for static HTML, with baseline gating and HTML/SARIF reports. Powered by @surea11y/core.

Readme

@surea11y/cli

Command-line WCAG 2.2 accessibility scanner for static HTML — baseline gating, HTML and SARIF reports, custom rules. Powered by @surea11y/core, the deterministic engine that tells you what it can't tell you.

npx @surea11y/cli scan ./index.html
npx @surea11y/cli scan https://example.com/ --tags wcag2a,wcag2aa

Install

npm install -g @surea11y/cli          # globally
npm install --save-dev @surea11y/cli  # or per-project, for CI

Requires Node.js ^20.19.0 || ^22.13.0 || >=24.0.0.

What it does

$ surea11y scan ./index.html

surea11y scan: file:///path/to/index.html
  pass: 6   fail: 3   cantTell: 4   notApplicable: 112

  occurrences by tier: fail: 3   cantTell: 5

FAIL (3 rule(s)):

  contrast-minimum  (serious, 1 fail occurrence(s))
    - html > body > main > p
      Element has insufficient color contrast of 2.85:1 (foreground: #999999,
      background: #ffffff, font size: 0px, font weight: normal). Expected
      contrast ratio of 4.5:1 (normal text).

  img-alt-present  (serious, 1 fail occurrence(s))
    - html > body > main > img
      Missing alt attribute on <img>.
      hint: Add an alt attribute (use alt="" only for decorative images).

cantTell — needs human review (4 rule(s)): contrast-computable, link-name-quality,
manual-review, page-title-patterns

That last line is the point of the engine: rules that cannot be settled from static markup are reported as cantTell and routed to a human, instead of being silently dropped or guessed at. cantTell never affects the exit code.

Exit code 0 = clean, 1 = at least one fail (the CI-gating case), 2 = usage error or the scan couldn't run.

What it can and can't scan

The CLI reads static HTML only — a local file, or the raw body of an HTTP(S) GET — and never executes page JavaScript. Client-rendered content won't be captured, and geometry-dependent rules report notApplicable (there's no real CSS layout under jsdom).

For client-rendered pages, drive a real browser and call the engine directly against the live DOM — see INTEGRATION.md Pattern 2, or use one of the ready-made bindings (Playwright, Puppeteer, Cypress, Selenium, WebdriverIO).

Common uses

# Target a conformance level
surea11y scan ./dist/index.html --tags wcag2a,wcag2aa

# Machine-readable output
surea11y scan ./dist/index.html --json > result.json

# Accept today's violations, gate CI only on new ones
surea11y scan ./dist/index.html --write-baseline baseline.json   # once, commit it
surea11y scan ./dist/index.html --baseline baseline.json         # in CI, from then on

# Browsable HTML report, and SARIF for GitHub Code Scanning
surea11y scan ./dist/index.html --html report.html --sarif results.sarif

# Your own org-specific rules
surea11y scan ./dist/index.html --custom-rules ./a11y-rules.js

Full flag reference, custom-rule contract, and CI recipes: docs/CLI.md.

Do you need this package?

You need it when you want a command instead of code. Two examples.

You want the CLI

A marketing site built to static HTML. The CI step is one shell line; there's no test file anywhere.

# .github/workflows/a11y.yml
- run: npm run build
- run: npx @surea11y/cli scan ./dist/index.html --tags wcag2a,wcag2aa

The scan exits 1 if anything fails, which fails the step. You wrote no JavaScript, so you needed the command.

You don't

A dashboard whose chart only exists after you click "Load report". You're already writing a Playwright test, so call the engine directly:

const { A11yCoreBuilder } = require('@surea11y/playwright');

test('dashboard is accessible after loading', async ({ page }) => {
  await page.goto('/dashboard');
  await page.getByRole('button', { name: 'Load report' }).click();

  const results = await new A11yCoreBuilder({ page }).analyze();
  expect(results.checksResults.filter((r) => r.outcome === 'fail')).toEqual([]);
});

No @surea11y/cli here. This is also CI — but the CLI would be useless for it, because it never executes page JavaScript. Point surea11y scan at that URL and it sees the page before the click ever happens: no chart, nothing to check.

The dividing line

It isn't local vs. CI — both examples are CI. It's:

  • Is what you want to scan present in the HTML the server sends? The CLI works, and it's the cheap path: no browser to launch.
  • Does it only appear after JavaScript runs? You need a real browser, so you need the library or a binding, and the CLI can't help.

That second case is why "use the CLI for pipelines" is a misleading shorthand — plenty of pipelines are the second example.

Either way you're running the same engine. @surea11y/core has zero runtime dependencies and evaluates whatever DOM you hand it, so if you already have a browser or a jsdom instance, you don't pay for jsdom twice. This package is the one that brings jsdom along, because turning fetched HTML into a DOM is what a CLI has to do. Splitting the two is what keeps that promise true.

Part of the SureA11y family

One engine, several front ends. If the dividing line above pointed you away from the CLI, install the one matching your setup instead — each pulls in @surea11y/core for you.

| Your setup | Install | |---|---| | Playwright | @surea11y/playwright | | Puppeteer | @surea11y/puppeteer | | Selenium | @surea11y/selenium | | Cypress | @surea11y/cypress | | WebdriverIO | @surea11y/webdriverio | | Jest or Vitest component tests | @surea11y/test-matchers | | Your own script, against a DOM you already have | @surea11y/core |

Local development

npm install
npm test

To run against a local, unpublished @surea11y/core — when you're changing the engine and the CLI together — pack it and install it without saving, so package.json keeps declaring its normal semver range:

(cd ../core && npm pack --pack-destination /tmp)
npm install /tmp/surea11y-core-<version>.tgz --no-save --no-package-lock
npm test

Never commit a file: dependency. Note that npm link is not a substitute here: it exposes the whole working directory and so ignores core's files allowlist, which can resolve paths that won't exist in the published package.

See RELEASE.md for the publish checklist and the cross-repo ordering constraint.

License

MIT. The engine, @surea11y/core, is MPL-2.0.