manni a11y
manni a11y check takes a URL, crawls the site behind it, and runs
axe-core against every page it reaches.
Each page gets a list of violations. Each violation carries the rule that fired,
the element it fired on, and axe’s own statement of what to change. The run
exits 1 when any page still has a violation, so the command you run locally is
the same one that gates a pull request.
What it checks, and what it leaves alone
Section titled “What it checks, and what it leaves alone”It checks what axe can decide from a rendered page. That is missing alternate text, buttons and links with no accessible name, form fields without labels, contrast, document language, and ARIA misuse.
It does not test keyboard flows, read your source, or write anything back. There
is no --fix, because the check never sees the Markdown, template, or component
tree that produced the page. Fix a failing check covers what
it hands the person who fixes the page, and what a green run does not prove.
axe runs inside a real browser, and installing @hawkeyexl/manni does not
download one. Get started has the one-time install.
How the crawl works
Section titled “How the crawl works”The crawl stays on the host of the URL you gave it. docs.example.com and
example.com are different hosts, and so is www.example.com; http:// and
https:// of one host are the same site. Give several seeds and every seed’s
host is in scope.
Pages come from two places, in this order:
- The sitemap. The site’s own list of pages, when it publishes one. It is looked up once per run, before the browser starts. See Sitemaps for when it is read, how it is found, and what happens without one.
- Links. Every page that loads contributes the same-host
<a href>links in its rendered DOM. Anchors,mailto:,tel:, and links to assets such as PDFs, images, and stylesheets are skipped.
Pages are checked one at a time, in the order they were discovered. Each URL is
checked once regardless of fragment, host casing, or a trailing slash. A page
the browser reached by redirect counts as checked too. There is no cap by
default, and the run goes on until nothing is queued. --max-pages <n> bounds
it, and anything still queued at the cap is reported as skipped, not failed.
--no-crawl checks exactly the URLs you named, with no sitemap lookup and no
link following.
A seed that does not load is exit 2, because nothing was checked. A crawled
page that does not load is recorded against that page and the crawl continues;
the page counts as failed, so the run exits 1.
Sitemaps
Section titled “Sitemaps”When it is looked up
Section titled “When it is looked up”Once per run, before the browser starts, and only when crawling. The lookup
uses the first seed’s origin and path. --no-crawl skips it and checks exactly
the URLs you named.
How it is found
Section titled “How it is found”Plain HTTP requests, no browser, each with a 10-second timeout. Candidates are tried in this order, and the first that answers 2xx and parses as a sitemap wins:
robots.txtat the origin, and everySitemap:line in it, in order.- Walking from the seed’s own directory up to the site root, nearest first,
the files
sitemap.xml,sitemap-index.xml, andsitemap_index.xmlin each directory.
For https://h/manni/meta/fix/ the walk asks /manni/meta/fix/, then
/manni/meta/, then /manni/, then /. The walk exists for project sites
published under a path. https://hawkeyexl.github.io/manni/ serves
/manni/sitemap-index.xml and nothing at the origin. A body that is 2xx but
not sitemap XML is skipped, and the next candidate is tried.
When one is there
Section titled “When one is there”A <sitemapindex> is followed into its child sitemaps, up to three levels deep
and fifty children in total. Every <loc> is kept when it is on the seed’s
host and looks like a page. That means http or https, and not an asset such
as a PDF, an image, or a stylesheet. Those URLs join the queue right after the
seeds, deduped against them with a trailing slash ignored. Link following still
runs on top, so a page the sitemap missed but a checked page links to is found
too. --max-pages caps the whole run. The header line names the sitemap that
was used, and summary.sitemap carries its URL.
When there is none
Section titled “When there is none”A 404, a network error, or an unparseable body all mean “no sitemap”. None of
them fails the run. The crawl falls back to same-host links from each checked
page, breadth first, and the header reads (no sitemap; followed links).
What it does not do yet
Section titled “What it does not do yet”Two gaps are recorded as deferred in proposal 0035. Disallow rules in
robots.txt are not honoured, and gzipped sitemaps (.xml.gz) are not
decompressed.
What the score means
Section titled “What the score means”Each page reports a score from 0 to 100. It is the share of axe rules that
applied to the page and passed. The --severity floor has already dropped the
violations you chose not to count:
score = round(100 × passes ÷ (passes + violations))It is not a Lighthouse score. Lighthouse weights rules; this does not. A
page with one error and thirty passing rules scores 97, and it still fails
the run. Read the score as a trend across pages and across runs, and
read the exit code as the verdict. A page that did not load, or where no rule
applied, has no score.
Rules axe could not decide (its incomplete results, such as contrast over a
background image) are counted and reported but never treated as failures.
Configuration
Section titled “Configuration”The a11y: key of manni.config.yaml sits beside meta: in the same file.
Every key is optional; a flag on the command line wins over the same key in
config, and config wins over the default.
a11y: urls: ["https://docs.example.com/"] # string[] seeds; fallback for [urls...] crawl: true # boolean default true maxPages: 500 # integer ≥ 1; absent means no cap tags: ["wcag2a", "wcag2aa"] # string[] severity: error # notice | warning | error timeout: 30000 # integer ≥ 1, mstags restricts axe to rules carrying any of those tags, so a gate can be
“WCAG 2.1 AA and nothing else”. severity is the floor: findings below it are
dropped before the score and the exit code are decided. Its values are the
family’s, notice, warning, and error, the same three every manni tool
uses. axe’s critical and serious both land as error, moderate as
warning, and minor as notice, and each finding keeps axe’s word as
impact. The configuration keys
table has the types and defaults.