Accessibility test toolkit

Script the CLI

Every a1 command prints one JSON object with --json, and every command sets its exit code from the same rules. A shell script or CI step can read either one.

JSON output

--json prints one object with ok, command, result on success, warnings, errors, and meta. Commands that load a page or control a screen reader session add target. mcp and help-all have no --json option and print text only.

a1 axe http://localhost:3000 --level AA --json
{
  "ok": true,
  "command": { "family": "axe", "subcommand": "axe", "version": "0.1.0", "wcagVersion": "2.2" },
  "target": { "kind": "url", "value": "http://localhost:3000/" },
  "result": { "violations": [], "passes": [], "incomplete": [], "inapplicable": [], "..." : "..." },
  "warnings": [],
  "errors": [],
  "meta": { "schemaVersion": "1", "startedAt": "...", "completedAt": "...", "durationMs": 842 }
}

A failure prints the same fields with ok: false and no result. errors has one or more entries with a code, a message, and sometimes details.

a1 wcag show 9.9.9 --json
{
  "ok": false,
  "command": { "family": "wcag", "subcommand": "show", "version": "0.1.0", "wcagVersion": "2.2" },
  "warnings": [],
  "errors": [
    { "code": "criterion-not-found", "message": "Criterion lookup failed for \"9.9.9\".", "details": { "lookupKey": "9.9.9" } }
  ],
  "meta": { "schemaVersion": "1", "startedAt": "...", "completedAt": "...", "durationMs": 13 }
}

Check ok, not the exit code, when a command can succeed with a failed check inside it. a1 axe keeps ok: true when violations remain after --fail-on filtering. The verdict is result.verdict, and the exit code returns the same verdict as a number, for a shell to check.

Exit codes

0The command ran and nothing it checked failed.
2Usage error: a bad flag, a missing required argument, a command the screen reader cannot run, or an sr command run with no active session.
3Environment error: the browser or the screen reader broker failed to start, a recording could not be written, or a required setup step is missing under a1 doctor --strict.
4A check failed: an a1 axe or a1 audit violation past --fail-on, or a failed sr find, sr goto, sr wait --for, sr expect, or sr batch step.
5An internal error a11ied did not classify. Report it as a bug with the command and the output.

Exit code 4 is the one to fail a build on. It means the command ran to completion and found something wrong, not that the command itself is broken.

a1 axe http://localhost:3000 --level AA --fail-on serious
echo "exit code: $?"

Shell scripts

A CI step or a pre-commit hook needs nothing beyond the exit code. No JSON parsing is required to fail a build.

#!/usr/bin/env bash
set -e

a1 axe http://localhost:3000/checkout --level AA --fail-on serious

For a one-line result from a command that moves the screen reader, --phrase prints the last announcement alone, with no JSON to parse. It needs an active session. sr start is the command that takes --sr.

a1 sr start --sr virtual http://localhost:3000/checkout --json
a1 sr next --phrase
heading, Checkout, level 1

Both text and JSON output print to stdout. A human-readable error line prints to stderr alongside the JSON, so a1 ... --json 2>/dev/null is safe to pipe into a JSON parser.

Reading JSON with jq

a1 axe http://localhost:3000 --level AA --json 2>/dev/null \
  | jq -r '.result.violations[] | "\(.id) (\(.impact))"'
button-name (critical)

result.verdict is the pass or fail decision after --fail-on and --baseline. Read it instead of counting violations yourself when a baseline is in play.

a1 audit http://localhost:3000 --json 2>/dev/null | jq -r '.result.nextCommands[]'
a1 wcag rule button-name

Baselines

axe and audit both take --baseline <file> and --update-baseline. On a site with existing findings, accept them once, then fail only on new ones.

a1 axe http://localhost:3000 --level AA --baseline a11y-baseline.json --update-baseline
git add a11y-baseline.json

Commit the file. Every later run without --update-baseline compares against it, keyed by rule id and node target, and exits 4 only for a finding the baseline does not already list.

a1 axe http://localhost:3000 --level AA --baseline a11y-baseline.json

Testing HTML without a server

axe, tree, and audit take a file path, - for HTML on stdin, or --html for inline markup, so a pre-commit hook can check a component file directly.

echo '<button></button>' | a1 axe - --json
a1 axe --html '<img src="logo.png">' --json
a1 axe src/components/Dialog.html --json