https://github.com/didrod205/a11ylint
Static accessibility (a11y) linter for HTML — WCAG-mapped checks (alt text, labels, heading order, ARIA, landmarks, color contrast, tables) with no headless browser. Deterministic CLI, JSON/Markdown reports, runs in CI.
https://github.com/didrod205/a11ylint
a11y a11y-ci a11y-linter accessibility accessibility-audit accessibility-linter aria axe-alternative cli html-accessibility section508 typescript wcag wcag-checker
Last synced: about 10 hours ago
JSON representation
Static accessibility (a11y) linter for HTML — WCAG-mapped checks (alt text, labels, heading order, ARIA, landmarks, color contrast, tables) with no headless browser. Deterministic CLI, JSON/Markdown reports, runs in CI.
- Host: GitHub
- URL: https://github.com/didrod205/a11ylint
- Owner: didrod205
- License: mit
- Created: 2026-06-01T03:37:12.000Z (about 1 month ago)
- Default Branch: main
- Last Pushed: 2026-06-29T07:23:20.000Z (6 days ago)
- Last Synced: 2026-06-29T09:16:46.223Z (6 days ago)
- Topics: a11y, a11y-ci, a11y-linter, accessibility, accessibility-audit, accessibility-linter, aria, axe-alternative, cli, html-accessibility, section508, typescript, wcag, wcag-checker
- Language: TypeScript
- Homepage: https://www.npmjs.com/package/@didrod2539/a11ylint
- Size: 63.5 KB
- Stars: 1
- Watchers: 0
- Forks: 0
- Open Issues: 0
-
Metadata Files:
- Readme: README.md
- Changelog: CHANGELOG.md
- Contributing: CONTRIBUTING.md
- Funding: .github/FUNDING.yml
- License: LICENSE
- Code of conduct: CODE_OF_CONDUCT.md
Awesome Lists containing this project
README
# ♿ a11ylint
### Catch accessibility (a11y) bugs in your HTML — no browser, right in CI.
[](https://www.npmjs.com/package/@didrod2539/a11ylint)
[](https://github.com/didrod205/a11ylint/actions/workflows/ci.yml)
[](https://www.npmjs.com/package/@didrod2539/a11ylint)
[](./LICENSE)
A deterministic, **static** accessibility linter for HTML. It checks WCAG-mapped
rules — alt text, form labels, heading order, ARIA validity, landmarks, color
contrast, data tables and more — **without a headless browser**, so it runs on
your build output in milliseconds. Score, A–F grade, and JSON/Markdown reports.
---
## One-line summary
`a11ylint` statically analyzes HTML files and reports WCAG accessibility issues
with line numbers, fixes, and a score you can gate in CI — no Puppeteer, no API
key, no server.
## Why this project exists
Accessibility isn't optional anymore: the **ADA** (US), the **European
Accessibility Act** (in force June 2025), **Section 508**, and similar laws make
inaccessible sites a legal and financial risk — and thousands of lawsuits are
filed every year. Yet most a11y tools (axe-core, pa11y, Lighthouse) need to spin
up a **headless browser**, which is slow, heavy, and awkward to wire into a build
that just emitted static HTML.
`a11ylint` focuses on the large set of issues you **can** catch from the markup
alone — missing alt text, unlabeled inputs, empty buttons/links, broken heading
order, invalid ARIA, missing landmarks, low-contrast inline styles, header-less
tables — and runs them as a fast, deterministic lint. Perfect for a pre-commit
hook or CI gate. It complements (doesn't replace) runtime tools.
## Key features
- 🖼️ **Images** — missing `alt`, redundant "image of…" alt text.
- 📝 **Forms** — inputs/selects/textareas with no associated label, empty labels.
- 🔤 **Structure** — skipped heading levels, empty headings, missing `
`,
missing `` landmark, invalid list nesting.
- 🔘 **Controls** — empty buttons/links, `` without `href`, positive `tabindex`.
- 🧩 **ARIA** — invalid roles, misspelled `aria-*` attributes, focusable content
inside `aria-hidden`.
- 🌐 **Language & meta** — missing `lang`/``, zoom-disabling viewport,
duplicate `id`s.
- 📊 **Tables** — data tables without ``/scope or ``.
- 🎨 **Color** — WCAG contrast math on inline `style` colors.
- Every issue maps to a **WCAG 2.1 success criterion** and level (A/AA/AAA).
- Score + **A–F grade**, **JSON/Markdown** export, **CI gate** exit codes.
## Install
```bash
# run without installing
npx @didrod2539/a11ylint scan index.html
# or install
npm install -g @didrod2539/a11ylint # global CLI (provides `a11ylint`)
npm install -D @didrod2539/a11ylint # project dev-dependency (for CI)
```
Node ≥ 18. ESM + CJS + TypeScript types.
## Quick start
```bash
a11ylint scan ./dist
```
```
page.html 80/100 (B)
Images & media 79
Forms & labels 76
Interactive controls 66
ARIA usage 70
...
✗ Image is missing an alt attribute:8 [WCAG 1.1.1 A]
→ Add alt text, or alt="" with role="presentation" if decorative.
✗ Form control has no associated label:19 [WCAG 3.3.2 A]
→ Add , wrap it in a , or use aria-label.
✗ Invalid ARIA role "buton":27 [WCAG 4.1.2 A]
⚠ Low contrast 1.92:1 (needs 4.5:1):25 [WCAG 1.4.3 AA]
Overall 80/100 (B) 1 page(s), 12 error(s), 8 warning(s), 2 info
```
## CLI usage
```bash
a11ylint scan [...targets] # lint HTML files or directories
a11ylint report # re-render a saved JSON report as Markdown
a11ylint init # scaffold a11ylint.config.json
a11ylint --help
a11ylint --version
```
`scan` options:
| Option | Description |
| --- | --- |
| `--config ` | Path to a config file (otherwise auto-detected) |
| `--level ` | Target WCAG conformance level (default AA) |
| `--json ` | Write a JSON report |
| `--md ` | Write a Markdown report |
| `--min-score ` | Exit non-zero if the overall score < n (CI gate) |
| `--quiet` | Hide info-level issues in the console |
Pointed at a directory, `scan` finds every `.html`/`.htm` recursively.
## Example result
Full reports for the bundled samples are in
[`examples/sample-report.md`](./examples/sample-report.md) and
[`examples/sample-report.json`](./examples/sample-report.json).
> 📸 _Screenshot / demo GIF placeholder:_ `./docs/screenshot.png` — record the
> terminal running `npx @didrod2539/a11ylint scan examples/bad.html`.
## Configuration
Create `a11ylint.config.json` (or run `a11ylint init`):
```json
{
"minLevel": "AA",
"minScore": 90,
"disableCategories": [],
"disableRules": ["img-redundant-alt"],
"ruleSeverity": { "table-caption": "warning" },
"categoryWeights": { "images": 1.2, "forms": 1.2, "controls": 1.2 }
}
```
| Field | Meaning |
| --- | --- |
| `minLevel` | Target WCAG level: `A` (A only), `AA` (A+AA), `AAA` (all) |
| `minScore` | CI gate threshold (overridable with `--min-score`) |
| `disableCategories` | Skip whole categories (e.g. `["color"]`) |
| `disableRules` | Skip individual rules by id |
| `ruleSeverity` | Override severity per rule id |
| `categoryWeights` | Re-weight categories in the overall score |
Categories: `images`, `forms`, `structure`, `controls`, `aria`, `language`,
`tables`, `color`. Run with `--help` or read `src/types.ts` for the full rule id
list.
## Real-world use cases
1. **Gate accessibility in CI.** Add `a11ylint scan ./dist --min-score 90` to
your pipeline. A PR that ships an unlabeled form field or an image without alt
text fails the build before it reaches users (or auditors).
2. **Audit a static export or template.** Run `a11ylint scan ./public
--md a11y-audit.md` to get a per-page, WCAG-referenced Markdown report you can
hand to a designer or compliance reviewer.
3. **Pre-commit safety net.** Wire `a11ylint scan .html` into a
pre-commit hook so regressions are caught at authoring time, no browser needed.
## Programmatic API
```ts
import { analyze, buildReport, toMarkdown } from "@didrod2539/a11ylint";
const page = analyze({ source: "index.html", html });
console.log(page.score, page.grade, page.issues);
const report = buildReport([page], { version: "0.1.0" });
await fs.writeFile("a11y.md", toMarkdown(report));
```
## Roadmap
- More rules: autocomplete tokens, `lang` on inline language changes, iframe
titles, label/placeholder-only inputs, redundant `role`.
- Contrast for `` blocks and class-based colors (lightweight CSS cascade).
- A GitHub Action + annotations on PR diffs.
- SARIF output for code-scanning integration.
- `--fix` for safe auto-fixes (add `scope`, quote ids, etc.).
- Config presets (`strict`, `recommended`).
## FAQ
**Is this a replacement for axe-core / Lighthouse?**
No — it's a **complement**. Those run in a real browser and catch dynamic and
computed-style issues a11ylint can't. a11ylint catches the large class of
**static, markup-level** problems with zero browser overhead, which makes it
ideal for CI and pre-commit. Use both.
**Does it need a browser or network?**
No. It parses HTML with a fast static parser and runs entirely locally — no
Puppeteer, no API key, no uploads.
**Why did my page score 80 with 12 errors?**
The score is per-category (each capped at 0–100) then weighted, so one terrible
category doesn't zero out a page that's otherwise fine. Tune `categoryWeights`
and `minScore` for your bar, and use `--min-score` to gate.
**Can it check color contrast?**
For colors declared in **inline `style`** attributes, yes (full WCAG math).
Contrast from external/embedded CSS needs the cascade and is on the roadmap.
**Does it understand ARIA?**
It validates role and `aria-*` attribute names against WAI-ARIA and flags
focusable content hidden with `aria-hidden`. Deep role-semantics checks are
planned.
## Contributing
Contributions welcome! Each check is a small, self-contained rule in
`src/rules/`, and WCAG mappings live in `src/wcag.ts`. See
[CONTRIBUTING.md](./CONTRIBUTING.md) and the
[Code of Conduct](./CODE_OF_CONDUCT.md).
```bash
git clone https://github.com/didrod205/a11ylint.git
cd a11ylint
npm install
npm test
npm run build
node dist/cli.js scan examples/bad.html
```
## License
[MIT](./LICENSE) © a11ylint contributors
## 💖 Sponsor
a11ylint is free, MIT-licensed, and built in spare time. If it helped you ship a
more accessible (and more compliant) site, please consider supporting it:
- ⭐ **Star this repo** — free, and it helps others find it.
- 🍋 **[Sponsor via Lemon Squeezy](https://elab-studio.lemonsqueezy.com/checkout/buy/5d059b89-51d0-456b-b33a-ed56994f7010)** — one-time or recurring.
**Where your support goes:** more WCAG rules, a GitHub Action with PR
annotations, SARIF output, CSS-aware contrast checking, a `--fix` mode, and fast
issue responses.