{"id":51417798,"url":"https://github.com/didrod205/a11ylint","last_synced_at":"2026-07-04T21:30:40.984Z","repository":{"id":368145401,"uuid":"1255647391","full_name":"didrod205/a11ylint","owner":"didrod205","description":"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.","archived":false,"fork":false,"pushed_at":"2026-06-29T07:23:20.000Z","size":65,"stargazers_count":1,"open_issues_count":0,"forks_count":0,"subscribers_count":0,"default_branch":"main","last_synced_at":"2026-06-29T09:16:46.223Z","etag":null,"topics":["a11y","a11y-ci","a11y-linter","accessibility","accessibility-audit","accessibility-linter","aria","axe-alternative","cli","html-accessibility","section508","typescript","wcag","wcag-checker"],"latest_commit_sha":null,"homepage":"https://www.npmjs.com/package/@didrod2539/a11ylint","language":"TypeScript","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/didrod205.png","metadata":{"files":{"readme":"README.md","changelog":"CHANGELOG.md","contributing":"CONTRIBUTING.md","funding":".github/FUNDING.yml","license":"LICENSE","code_of_conduct":"CODE_OF_CONDUCT.md","threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null,"notice":null,"maintainers":null,"copyright":null,"agents":null,"dco":null,"cla":null},"funding":{"github":"didrod205"}},"created_at":"2026-06-01T03:37:12.000Z","updated_at":"2026-06-29T07:23:24.000Z","dependencies_parsed_at":null,"dependency_job_id":null,"html_url":"https://github.com/didrod205/a11ylint","commit_stats":null,"previous_names":["didrod205/a11ylint"],"tags_count":1,"template":false,"template_full_name":null,"purl":"pkg:github/didrod205/a11ylint","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/didrod205%2Fa11ylint","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/didrod205%2Fa11ylint/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/didrod205%2Fa11ylint/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/didrod205%2Fa11ylint/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/didrod205","download_url":"https://codeload.github.com/didrod205/a11ylint/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/didrod205%2Fa11ylint/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":35136712,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-05-26T15:22:16.424Z","status":"online","status_checked_at":"2026-07-04T02:00:05.987Z","response_time":113,"last_error":null,"robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":true,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["a11y","a11y-ci","a11y-linter","accessibility","accessibility-audit","accessibility-linter","aria","axe-alternative","cli","html-accessibility","section508","typescript","wcag","wcag-checker"],"created_at":"2026-07-04T21:30:40.210Z","updated_at":"2026-07-04T21:30:40.909Z","avatar_url":"https://github.com/didrod205.png","language":"TypeScript","funding_links":["https://github.com/sponsors/didrod205"],"categories":[],"sub_categories":[],"readme":"\u003cdiv align=\"center\"\u003e\n\n# ♿ a11ylint\n\n### Catch accessibility (a11y) bugs in your HTML — no browser, right in CI.\n\n[![npm version](https://img.shields.io/npm/v/@didrod2539/a11ylint.svg?color=success)](https://www.npmjs.com/package/@didrod2539/a11ylint)\n[![CI](https://github.com/didrod205/a11ylint/actions/workflows/ci.yml/badge.svg)](https://github.com/didrod205/a11ylint/actions/workflows/ci.yml)\n[![node](https://img.shields.io/node/v/@didrod2539/a11ylint.svg)](https://www.npmjs.com/package/@didrod2539/a11ylint)\n[![license](https://img.shields.io/npm/l/@didrod2539/a11ylint.svg)](./LICENSE)\n\nA deterministic, **static** accessibility linter for HTML. It checks WCAG-mapped\nrules — alt text, form labels, heading order, ARIA validity, landmarks, color\ncontrast, data tables and more — **without a headless browser**, so it runs on\nyour build output in milliseconds. Score, A–F grade, and JSON/Markdown reports.\n\n\u003c/div\u003e\n\n---\n\n## One-line summary\n\n`a11ylint` statically analyzes HTML files and reports WCAG accessibility issues\nwith line numbers, fixes, and a score you can gate in CI — no Puppeteer, no API\nkey, no server.\n\n## Why this project exists\n\nAccessibility isn't optional anymore: the **ADA** (US), the **European\nAccessibility Act** (in force June 2025), **Section 508**, and similar laws make\ninaccessible sites a legal and financial risk — and thousands of lawsuits are\nfiled every year. Yet most a11y tools (axe-core, pa11y, Lighthouse) need to spin\nup a **headless browser**, which is slow, heavy, and awkward to wire into a build\nthat just emitted static HTML.\n\n`a11ylint` focuses on the large set of issues you **can** catch from the markup\nalone — missing alt text, unlabeled inputs, empty buttons/links, broken heading\norder, invalid ARIA, missing landmarks, low-contrast inline styles, header-less\ntables — and runs them as a fast, deterministic lint. Perfect for a pre-commit\nhook or CI gate. It complements (doesn't replace) runtime tools.\n\n## Key features\n\n- 🖼️ **Images** — missing `alt`, redundant \"image of…\" alt text.\n- 📝 **Forms** — inputs/selects/textareas with no associated label, empty labels.\n- 🔤 **Structure** — skipped heading levels, empty headings, missing `\u003ch1\u003e`,\n  missing `\u003cmain\u003e` landmark, invalid list nesting.\n- 🔘 **Controls** — empty buttons/links, `\u003ca\u003e` without `href`, positive `tabindex`.\n- 🧩 **ARIA** — invalid roles, misspelled `aria-*` attributes, focusable content\n  inside `aria-hidden`.\n- 🌐 **Language \u0026 meta** — missing `lang`/`\u003ctitle\u003e`, zoom-disabling viewport,\n  duplicate `id`s.\n- 📊 **Tables** — data tables without `\u003cth\u003e`/scope or `\u003ccaption\u003e`.\n- 🎨 **Color** — WCAG contrast math on inline `style` colors.\n- Every issue maps to a **WCAG 2.1 success criterion** and level (A/AA/AAA).\n- Score + **A–F grade**, **JSON/Markdown** export, **CI gate** exit codes.\n\n## Install\n\n```bash\n# run without installing\nnpx @didrod2539/a11ylint scan index.html\n\n# or install\nnpm install -g @didrod2539/a11ylint    # global CLI (provides `a11ylint`)\nnpm install -D @didrod2539/a11ylint    # project dev-dependency (for CI)\n```\n\nNode ≥ 18. ESM + CJS + TypeScript types.\n\n## Quick start\n\n```bash\na11ylint scan ./dist\n```\n\n```\npage.html  80/100 (B)\n  Images \u0026 media          79\n  Forms \u0026 labels          76\n  Interactive controls    66\n  ARIA usage              70\n  ...\n\n  ✗ Image is missing an alt attribute:8 [WCAG 1.1.1 A]\n      → Add alt text, or alt=\"\" with role=\"presentation\" if decorative.\n  ✗ Form control \u003cinput type=\"text\"\u003e has no associated label:19 [WCAG 3.3.2 A]\n      → Add \u003clabel for=\"…\"\u003e, wrap it in a \u003clabel\u003e, or use aria-label.\n  ✗ Invalid ARIA role \"buton\":27 [WCAG 4.1.2 A]\n  ⚠ Low contrast 1.92:1 (needs 4.5:1):25 [WCAG 1.4.3 AA]\n\nOverall  80/100 (B)  1 page(s), 12 error(s), 8 warning(s), 2 info\n```\n\n## CLI usage\n\n```bash\na11ylint scan [...targets]    # lint HTML files or directories\na11ylint report \u003cinput.json\u003e  # re-render a saved JSON report as Markdown\na11ylint init                 # scaffold a11ylint.config.json\na11ylint --help\na11ylint --version\n```\n\n`scan` options:\n\n| Option | Description |\n| --- | --- |\n| `--config \u003cfile\u003e` | Path to a config file (otherwise auto-detected) |\n| `--level \u003cA\\|AA\\|AAA\u003e` | Target WCAG conformance level (default AA) |\n| `--json \u003cfile\u003e` | Write a JSON report |\n| `--md \u003cfile\u003e` | Write a Markdown report |\n| `--min-score \u003cn\u003e` | Exit non-zero if the overall score \u003c n (CI gate) |\n| `--quiet` | Hide info-level issues in the console |\n\nPointed at a directory, `scan` finds every `.html`/`.htm` recursively.\n\n## Example result\n\nFull reports for the bundled samples are in\n[`examples/sample-report.md`](./examples/sample-report.md) and\n[`examples/sample-report.json`](./examples/sample-report.json).\n\n\u003e 📸 _Screenshot / demo GIF placeholder:_ `./docs/screenshot.png` — record the\n\u003e terminal running `npx @didrod2539/a11ylint scan examples/bad.html`.\n\n## Configuration\n\nCreate `a11ylint.config.json` (or run `a11ylint init`):\n\n```json\n{\n  \"minLevel\": \"AA\",\n  \"minScore\": 90,\n  \"disableCategories\": [],\n  \"disableRules\": [\"img-redundant-alt\"],\n  \"ruleSeverity\": { \"table-caption\": \"warning\" },\n  \"categoryWeights\": { \"images\": 1.2, \"forms\": 1.2, \"controls\": 1.2 }\n}\n```\n\n| Field | Meaning |\n| --- | --- |\n| `minLevel` | Target WCAG level: `A` (A only), `AA` (A+AA), `AAA` (all) |\n| `minScore` | CI gate threshold (overridable with `--min-score`) |\n| `disableCategories` | Skip whole categories (e.g. `[\"color\"]`) |\n| `disableRules` | Skip individual rules by id |\n| `ruleSeverity` | Override severity per rule id |\n| `categoryWeights` | Re-weight categories in the overall score |\n\nCategories: `images`, `forms`, `structure`, `controls`, `aria`, `language`,\n`tables`, `color`. Run with `--help` or read `src/types.ts` for the full rule id\nlist.\n\n## Real-world use cases\n\n1. **Gate accessibility in CI.** Add `a11ylint scan ./dist --min-score 90` to\n   your pipeline. A PR that ships an unlabeled form field or an image without alt\n   text fails the build before it reaches users (or auditors).\n2. **Audit a static export or template.** Run `a11ylint scan ./public\n   --md a11y-audit.md` to get a per-page, WCAG-referenced Markdown report you can\n   hand to a designer or compliance reviewer.\n3. **Pre-commit safety net.** Wire `a11ylint scan \u003cchanged\u003e.html` into a\n   pre-commit hook so regressions are caught at authoring time, no browser needed.\n\n## Programmatic API\n\n```ts\nimport { analyze, buildReport, toMarkdown } from \"@didrod2539/a11ylint\";\n\nconst page = analyze({ source: \"index.html\", html });\nconsole.log(page.score, page.grade, page.issues);\n\nconst report = buildReport([page], { version: \"0.1.0\" });\nawait fs.writeFile(\"a11y.md\", toMarkdown(report));\n```\n\n## Roadmap\n\n- More rules: autocomplete tokens, `lang` on inline language changes, iframe\n  titles, label/placeholder-only inputs, redundant `role`.\n- Contrast for `\u003cstyle\u003e` blocks and class-based colors (lightweight CSS cascade).\n- A GitHub Action + annotations on PR diffs.\n- SARIF output for code-scanning integration.\n- `--fix` for safe auto-fixes (add `scope`, quote ids, etc.).\n- Config presets (`strict`, `recommended`).\n\n## FAQ\n\n**Is this a replacement for axe-core / Lighthouse?**\nNo — it's a **complement**. Those run in a real browser and catch dynamic and\ncomputed-style issues a11ylint can't. a11ylint catches the large class of\n**static, markup-level** problems with zero browser overhead, which makes it\nideal for CI and pre-commit. Use both.\n\n**Does it need a browser or network?**\nNo. It parses HTML with a fast static parser and runs entirely locally — no\nPuppeteer, no API key, no uploads.\n\n**Why did my page score 80 with 12 errors?**\nThe score is per-category (each capped at 0–100) then weighted, so one terrible\ncategory doesn't zero out a page that's otherwise fine. Tune `categoryWeights`\nand `minScore` for your bar, and use `--min-score` to gate.\n\n**Can it check color contrast?**\nFor colors declared in **inline `style`** attributes, yes (full WCAG math).\nContrast from external/embedded CSS needs the cascade and is on the roadmap.\n\n**Does it understand ARIA?**\nIt validates role and `aria-*` attribute names against WAI-ARIA and flags\nfocusable content hidden with `aria-hidden`. Deep role-semantics checks are\nplanned.\n\n## Contributing\n\nContributions welcome! Each check is a small, self-contained rule in\n`src/rules/`, and WCAG mappings live in `src/wcag.ts`. See\n[CONTRIBUTING.md](./CONTRIBUTING.md) and the\n[Code of Conduct](./CODE_OF_CONDUCT.md).\n\n```bash\ngit clone https://github.com/didrod205/a11ylint.git\ncd a11ylint\nnpm install\nnpm test\nnpm run build\nnode dist/cli.js scan examples/bad.html\n```\n\n## License\n\n[MIT](./LICENSE) © a11ylint contributors\n\n## 💖 Sponsor\n\na11ylint is free, MIT-licensed, and built in spare time. If it helped you ship a\nmore accessible (and more compliant) site, please consider supporting it:\n\n- ⭐ **Star this repo** — free, and it helps others find it.\n- 🍋 **[Sponsor via Lemon Squeezy](https://elab-studio.lemonsqueezy.com/checkout/buy/5d059b89-51d0-456b-b33a-ed56994f7010)** — one-time or recurring.\n\n**Where your support goes:** more WCAG rules, a GitHub Action with PR\nannotations, SARIF output, CSS-aware contrast checking, a `--fix` mode, and fast\nissue responses.\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fdidrod205%2Fa11ylint","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fdidrod205%2Fa11ylint","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fdidrod205%2Fa11ylint/lists"}