GitHub Packages · MIT · Node ≥ 18

a11y-warden

WCAG 2.2 accessibility audit of static HTML for CI, with a baseline so you can adopt it on an existing site and only fail on new problems.

  1. 1 Point the @mrlouiswright scope at GitHub Packages in .npmrc

    @mrlouiswright:registry=https://npm.pkg.github.com
  2. 2 Install

    npm i -D @mrlouiswright/a11y-warden
Installing from GitHub Packages needs a token

Published to GitHub Packages, not npmjs.com. GitHub Packages needs a GitHub token with read:packages to install, even for public packages: add //npm.pkg.github.com/:_authToken=${GITHUB_TOKEN} to .npmrc or run npm login --registry=https://npm.pkg.github.com.

~/project — zsh
$ npx a11y-warden test/fixtures/bada11y-warden — /workspace/a11y-warden/test/fixtures/bad2 page(s) audited HIGH     index.html:1 meta viewport "width=device-width, user-scalable=no" blocks zooming [viewport-zoom-disabled]         ↳ fix: Remove user-scalable=no and maximum-scale.HIGH     index.html:2 <img src="hero.jpg"> has no alt [img-alt-missing]         ↳ fix: Add alt text describing the image, or alt="" if it is decorative.HIGH     index.html:3 <input name="q"> has no label (placeholder is not a label) [input-label-missing]         ↳ fix: Add <label for="id">, wrap it in <label>, or use aria-label.HIGH     index.html:3 <select name="s"> has no label [input-label-missing]
Sample output from the a11y-warden README.

Why it exists

Most accessibility regressions are simple and machine-detectable: an image without alt text, an icon button with no name, a form field whose only label is its placeholder, grey-on-white text, a modal hidden with aria-hidden that still traps keyboard focus. a11y-warden reads every built HTML page, checks it against a focused set of WCAG 2.2 success criteria, and reports each problem with the file, line, severity, the criterion and a concrete fix. Record existing issues in a baseline file and CI fails only on new ones.

CLI, library and GitHub Action. Node.js 18+, zero runtime dependencies. Works on any static build output (Astro, Next.js “output: 'export'”, Eleventy, Hugo, Jekyll, Docusaurus, VitePress, plain HTML) and runs offline: point it at the folder your build writes. By Louis Wright.

Features

Text alternatives (1.1.1)

missing alt, file-name or placeholder alt (IMG_1234.png, "image of…"), unnamed <svg role="img">, <input type="image">.

Names and labels (1.3.1, 2.4.4, 4.1.2)

form controls without label/aria-label/aria-labelledby/title (placeholders do not count), buttons and links without accessible names, generic link text, iframes without title.

Contrast (1.4.3)

inline color against the nearest inline background-color (or a configurable default), with the 3:1 large-text exception.

Structure (1.3.1, 2.4.1, 2.4.2, 3.1.1)

<html lang>, <title>, a single <main>, data tables without <th>.

ARIA and focus (2.4.3, 2.4.7, 4.1.2)

invalid roles, aria-labelledby/describedby/controls and <label for> pointing at missing ids, duplicate referenced ids, aria-hidden containing focusable elements, positive tabindex, inline outline: none.

Zoom and media (1.4.2, 1.4.4)

viewport that disables zoom, media that autoplays with sound and no controls.

Baseline

--update-baseline records current findings; later runs ignore them and report only new ones. Hidden content (hidden, aria-hidden) is skipped.

Usage

# Example

 npx a11y-warden dist

# record existing issues once

 npx a11y-warden dist --update-baseline

# Example

 npx a11y-warden dist --baseline .a11y-baseline.json --fail-on medium

# Example

 npx a11y-warden _site --format sarif > a11y.sarif

CI / GitHub Action

permissions:
  contents: read
  security-events: write   # only needed for the SARIF upload
jobs:
  a11y-warden:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@11d5960a326750d5838078e36cf38b85af677262 # v4
        with: { persist-credentials: false }
      - run: npm ci && npm run build   # produce the static site first
      - uses: MrLouisWright/a11y-warden@v1
        with:
          path: dist
          fail-on: high
          sarif: a11y-warden.sarif
      - uses: github/codeql-action/upload-sarif@v3
        if: always()
        with: { sarif_file: a11y-warden.sarif }

Options & flags

FlagDescription
Site
--base-url <url>Public URL of the site, e.g. https://example.com (default: inferred from canonical/og:url)
--include <globs>Only check these pages (file or URL path globs, comma-separated)
--exclude <globs>Skip these pages
--clean-urlsPages are served without .html (about.html -> /about)
Baseline
--baseline <file>Ignore findings recorded in this baseline file (default: .a11y-baseline.json if present)
--update-baselineWrite all current findings to the baseline file and exit 0
--strict-idsReport every duplicate id, not only referenced ones
Common
-c, --config <file>JSON config (default: .a11y-warden.json or "a11yWarden" in package.json)
-f, --format <fmt>text (default) | json | sarif | markdown
--json <file>Also write a JSON report
--sarif <file>Also write SARIF 2.1.0
--markdown <file>Also write Markdown (e.g. for a job summary)
--fail-on <sev>critical | high | medium | low | info | none (default: high)
--list-rulesPrint rules and default severities
--no-fixesHide fix suggestions in text output
--no-colorDisable colours

Full reference, config file format and API in the README.

Support this project

Support open-source security tools

I build free, open-source tools that help developers ship safer websites. Every coffee helps keep them maintained, documented and free for everyone.

Buy me a coffee (opens Stripe in a new tab)

Secure checkout by Stripe

Portrait of Louis Wright

About the author

Louis Wright

Cyber security researcher and software engineer based in the UK.

More tools by Louis Wright

58 tools

Sentinels security scanners & CI gates · 20