a11y-ad — browserless WCAG 4.1.2 accessible-name auditor
a11y-ad audits HTML for missing accessible names (WCAG 4.1.2 / "Name,
Role, Value") on every interactive element — no browser, no headless Chrome,
stdlib only. Built for CI.
Why another accessibility tool?
Most lightweight linters check only aria-label/<label> and flag every
button with visible text as a critical failure. In one audit run against a
fully-labelled application, a popular scanner reported 68 "critical" HTML
accessible-name issues — 0 of them real, because visible button text,
aria-labelledby, and wrapping <label> elements are all valid accessible
names.
a11y-ad resolves the name the way assistive technology does, following the
WCAG 4.1.2 resolution order:
aria-labelledby -> aria-label -> visible text -> <label for=id>
-> wrapping <label> -> title -> alt (input[type=image])
and reports only the elements that end up with no name at all.
Scope (be honest about automated a11y coverage)
Automated tools in general catch roughly 30–50% of WCAG issues; manual
keyboard/screen-reader testing remains necessary. a11y-ad covers exactly
one failure class — missing accessible names — and does it with zero false
positives in the resolution cases above. Browser-measured checks (target
size, focus visibility, contrast, reduced motion) are out of scope for the
core; see Roadmap.
Install
pip install a11y-ad
Usage
a11y-ad index.html # single file
a11y-ad src/ # directory (recursive, *.html)
a11y-ad https://example.com # URL
a11y-ad src/ --json # machine-readable
Exit code is 1 when any interactive element lacks a name — use it as a CI
gate:
# GitHub Actions example
- run: pip install a11y-ad && a11y-ad dist/
JSON payload
[
{
"target": "bad.html",
"total": 3,
"missing": ["line 3 <button >", "line 4 <input >"],
"sources": {"MISSING": 2, "text": 1},
"ok": false
}
]
Library
from a11y_ad import audit, audit_file, audit_tree, fetch
r = audit('<button aria-labelledby="h1">Edit</button>')
r.ok # True
r.elements[0] # Element(tag='button', name='...', source='aria-labelledby')
r.missing # elements with no accessible name
Exclusions & semantics
<a>withouthrefand<input type="hidden">are not interactive → skipped.aria-hidden="true"counts as named (it is intentionally hidden from AT).- Elements whose visible text lives in a translation dictionary (filled at runtime) will read as statically missing; audit the rendered DOM for the final word (see Roadmap).
Security
fetch() refuses non-http(s) schemes and any host resolving to a loopback,
private, link-local, reserved or multicast address, so the library can be
wired into "enter your URL" scan forms without handing callers a probe into
the host network. The guard is best-effort (no DNS-rebinding defense) — for
untrusted input at scale, front it with a proxy allowlist.
Roadmap
--i18nruntime layer: apply a key/value dictionary before auditing (ported from the battle-tested original).- GitHub Action wrapper.
- Browser-measured WCAG 2.2 checks (2.5.8 target size, 2.4.7 focus,
1.4.3 contrast) as an optional
[browser]extra (Playwright).
Provenance
The resolver is a hardened port of an auditor written for a production
PWA, where four measurement bugs (void-tag stack shifting, early
</span> closure bypassing a wrapping <label>, missing label[for]
map, wrapping labels not recognized) were found via live measurement and
fixed; their regression scenarios ship as tests in this repository.
License
Apache-2.0
Metadata
Release files for a11y-ad 0.1.0
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | |
|---|---|---|---|
| a11y_ad-0.1.0.tar.gz | 16.9 kB | Details |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Reset |
|---|---|---|---|---|
| a11y_ad-0.1.0-py3-none-any.whl | Python 3 | none | any | Details |
Total release size: 30.3 kB
Release files / a11y_ad-0.1.0.tar.gz
| Download URL | a11y_ad-0.1.0.tar.gz |
|---|---|
| Size | 16.9 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
9c482d64c401de9964d219817a7afffb3e5229295920de30160a92b1e3ed7412
|
|
BLAKE2b-256 checksum How to use checksums |
17f00097249ebe00077f350f5b4887ca46c0d68fbb89a97d7617dd00157bdc9f
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
twine/7.0.0 CPython/3.14.7
|
Release files / a11y_ad-0.1.0-py3-none-any.whl
| Download URL | a11y_ad-0.1.0-py3-none-any.whl |
|---|---|
| Size | 13.4 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
6a9940f7bdf4a6cd760cda14fe8ee3da9ae16ed79a788b3ec535bbb785b65eaf
|
|
BLAKE2b-256 checksum How to use checksums |
7ce143f2edc3d4e4b8eb15f23f3f1754ad515a61b3433de7aef5f4f61d6ad4a6
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
twine/7.0.0 CPython/3.14.7
|