a11y.config.json
Your accessibility policy, committed to your repo — the Jest/ESLint model. a11y.config.json names the routes you audit and seeds the flags every command runs with, so a configured repo audits itself with no flags: a bare real-a11y audit walks the whole list, each view command inherits your emulation and wait settings, and snapshot diffs the same routes run to run.
The file is strict JSON — no JS config, no eval — and fail-closed: an unknown or mistyped key ("failon" for failOn, a bad value like failOn: "sometimes") is a hard error, never a silent no-op. A silently-ignored gate key could un-gate CI, so the loader refuses to guess. It's repo-controlled and low-trust, hence the guardrails: a 1 MB file cap, a 100-entry urls cap, and a compile-check on every redact pattern at load — a bad regex fails on startup, not mid-run.
The config is optional. Without one, every command works from flags and positional URLs alone.
Discovery. Auto-discovered as ./a11y.config.json in the directory you run from — no upward walk in v1, so you inherit only a config in the current directory, never a parent's. --config <file> points elsewhere; --no-config ignores a discovered one. Both flags are accepted on every command.
Precedence is flag > env > config defaults > built-in. An explicit flag always wins, so a config default is a floor you override per run, never a ceiling you can't escape. (The env layer is A11Y_PAGES for the page list and A11Y_SNAPSHOT_OUT for snapshot's output — see urls.)
A minimal, complete config:
{
"urls": [
"http://localhost:3000",
{ "name": "Login", "url": "http://localhost:3000/login", "rootSelector": "main" }
],
"defaults": {
"device": "iPhone 13",
"waitUntil": "networkidle",
"rules": ["no-unlabeled-interactive", "image-alt"],
"failOn": "error"
},
"redact": ["token=[^&]+"]
}Top-level keys
Three keys: urls (what to audit), defaults (how to audit it), and redact (what to strip from output). Everything else is rejected — except four back-compat aliases (the old pages, plus three top-level shorthands); see Legacy shorthands.
urls
urls: (string | { url, name?, rootSelector?, sourcePath? })[] — optional · non-empty · max 100 entries
The project's audit targets. Each entry is a bare URL string, or an object with a required url plus optional fields. A bare string's name defaults to the URL itself.
A configured urls list drives a bare audit and snapshot — run either with no positional and it audits every entry, so you don't re-type routes. Single-view commands (tree / outline / tabs / list) still take exactly one URL. diff reads two snapshot artifacts, not this list (but it does read defaults).
Entry fields:
url— the target. Any URL the browser can reach: a public site, a local dev server, staging, or a built file path.name— the diff join key and display label. This is what pairs a route across two snapshots, so keep it stable when a URL changes. Defaults tourl.rootSelector— scope extraction to a region for this route (per-pageroot); the semantic tree is taken from the matching element down.sourcePath— repo-relative file the route's findings anchor to in SARIF. GitHub code scanning only displays results tied to a file path; without it, results anchor to the config file.
{
"urls": [
"https://example.com",
{ "name": "Dashboard", "url": "http://localhost:3000/app", "rootSelector": "#root", "sourcePath": "src/pages/App.tsx" }
]
}Precedence for the audited list: positional URLs → the A11Y_PAGES env var (JSON [{name,url}]) → this urls list. Positionals are gated as arg; env and config go through the stricter config URL gate.
defaults
defaults: object — optional · always materialized (possibly empty)
Seeds a value for any flag you don't pass, on every command that declares it — the Jest/ESLint "virtual flag" model. Each key mirrors a CLI flag; the merge injects the value in the flag's raw shape and hands it to the command's own parser, so a config default is validated exactly like a typed flag would be. See defaults keys for the full table.
Two merge rules keep precedence honest:
- Explicit flag wins. A default only fills a flag you left unset — it never overwrites
--deviceyou typed. - Only reaches commands that accept the flag. An emulation default (e.g.
device) can't seedlogin, which declares no emulation flags — so a default never trips a parser the equivalent flag couldn't.
A defaults-only config (no urls) is valid — useful when you drive URLs positionally but want shared emulation and gate settings.
redact
redact: string[] — optional
Extra regex patterns applied to URLs and text before anything is printed or written — for stripping session tokens, signed-URL query params, or PII that would otherwise land in a report or a committed snapshot. Each pattern is new RegExp-compiled at load, so an invalid pattern fails on startup rather than mid-audit.
{ "redact": ["token=[^&]+", "\\bsig=[A-Za-z0-9]+"] }defaults keys
Every key below maps to one CLI flag. The meta line gives its JSON type, the flag it seeds, the built-in default it overrides, and the commands it reaches — a default only seeds a command that declares that flag. Throughout, the view commands means tree / outline / tabs / list. Order and types are exactly the loader's allowlist; anything outside it is rejected at load.
Two path-valued keys — storageState and baseline — resolve relative to the config file's directory, so a committed config is portable no matter which directory a command runs from.
root
root: string — flag --root · default body · applies to audit, inspect, the view commands, snapshot
Scope extraction to a CSS selector — audit a single region or component instead of the whole page. For per-route scoping, prefer urls[].rootSelector.
device
device: string — flag --device · default: unset · applies to audit, inspect, the view commands, snapshot
Emulate a Playwright device profile — "iPhone 13", "Pixel 7" — to audit what a screen reader hears on the mobile layout. Suppressed when you pass --cdp (which reuses a running browser and can't emulate).
{ "defaults": { "device": "iPhone 13" } }viewport
viewport: string — flag --viewport · default: unset · applies to audit, inspect, the view commands, snapshot
Explicit viewport as WIDTHxHEIGHT, e.g. "1280x800". Suppressed when you pass --cdp.
waitUntil
waitUntil: string — flag --wait-until · default load · applies to audit, inspect, the view commands, snapshot, login
Navigation settle state before extraction: load | domcontentloaded | networkidle | commit. Bump to networkidle for JS-heavy SPAs whose accessible content lands after first paint.
settleMs
settleMs: number — flag --settle · default 0 · max 30000 · applies to audit, inspect, the view commands, snapshot, login
Extra milliseconds to wait after waitUntil resolves — for content that animates or hydrates in after the load event.
timeoutMs
timeoutMs: number — flag --timeout · default 30000 · max 120000 · applies to audit, inspect, the view commands, snapshot, login
Navigation timeout in milliseconds.
headful
headful: boolean — flag --headful · default false · applies to audit, inspect, the view commands, snapshot
Show the browser window instead of running headless. Suppressed when you pass --cdp.
storageState
storageState: string — flag --storage-state · default: unset · path (resolves from the config dir) · applies to audit, inspect, the view commands, snapshot
Audit as a logged-in user, using a session file saved by real-a11y login. The path resolves relative to the config file, so a committed config stays portable. Suppressed when you pass --cdp (CDP reuses the running browser's own session). See Authenticated pages.
{ "defaults": { "storageState": "./.auth/session.json" } }auditOrigins
auditOrigins: string[] — flag --audit-origin (repeatable) · default: the target's own origin · applies to audit, inspect, the view commands, snapshot
Extra origins allowed under storageState — origin pinning that stops a redirect from routing extraction to an unintended, cookie-matching origin. Each entry must be a full origin like https://app.example.com.
format
format: string — flag -f, --format · default pretty (json on snapshot) · applies to audit, inspect, the view commands, snapshot, diff
Output format. Validated per command, so a global default only has to be valid where it lands: audit / inspect / view commands take pretty | json; snapshot takes json | md | sarif | junit | jsonl; diff takes pretty | json | md. A project-wide format: "sarif" seeds snapshot cleanly but errors on audit. Suppressed on snapshot when you pass the --md shorthand.
rules
rules: string[] — flag --rules · default: all five rules · applies to audit, inspect, snapshot
Restrict the run to a subset of the semantic-tree rules. Valid ids: no-unlabeled-interactive, image-alt, heading-order, dialog-labeled, landmark-structure — an unknown id is a load error.
{ "defaults": { "rules": ["no-unlabeled-interactive", "image-alt"] } }failOn
failOn: "error" | "warning" | "never" — flag --fail-on · default error (never on snapshot) · applies to audit, inspect, snapshot, diff
The gate threshold — the lowest severity that exits 1. snapshot defaults to never (it just writes the artifact); set it there to gate the snapshot itself. View commands are never gates and always exit 0, so this doesn't reach them.
annotate
annotate: boolean — flag --no-annotate (as annotate: false) · default true · applies to audit, inspect
GitHub Actions annotations (grouped ::error lines + a job summary), on by default. Only annotate: false is meaningful — it seeds --no-annotate; annotate: true is the built-in and seeds nothing.
includeGeneric
includeGeneric: boolean — flag --include-generic · default false · applies to inspect, the view commands, snapshot
Keep generic container nodes (no role, no name) in the serialized tree. Off by default so output shows only what a screen reader announces. Declared on every view command; only tree-rendering output (tree, inspect, snapshot) actually consumes it.
baseline
baseline: string — flag --baseline · default: unset · path (resolves from the config dir) · applies to snapshot, diff
Suppress findings a baseline file has accepted — adopt the gate on a codebase with existing debt without silencing new regressions. Suppressed findings stay in the report (marked "suppressed": true), just out of the --fail-on count. Path resolves relative to the config file. See Adopt the gate on existing debt.
ignoreViewLine
ignoreViewLine: string[] — flag --ignore-view-line (repeatable) · default: none · applies to diff
Drop view lines matching these regexes before diffing — for generated content that changes every build (a "last updated" timestamp, a build hash) and would otherwise read as drift on every page.
{ "defaults": { "ignoreViewLine": ["^time \""] } }maxLines
maxLines: number — flag --max-lines · default: full · applies to diff
Cap each page's structural diff to n lines, then … N more. For CI comments — run once uncapped to a log, once capped to the comment. See the CI diff bot guide.
maxPages
maxPages: number — flag --max-pages · default: all · applies to diff
Detail at most n changed routes, then list the rest by name. For CI comments.
explain
explain: boolean — flag --explain · default false · applies to diff
Add the plain-language structural summary to a diff ("Heading level changed: h2 → h3", "Keyboard tab stop removed …"). Opt-in because it's an interpretive layer; the neutral default makes no claim the raw diff can't back up.
Not settable
Five declared flags are deliberately excluded from defaults — no config key can populate them:
--output/--quiet/--verbose— per-run I/O and log-verbosity concerns, not project policy. Where output goes and how loud a run is belong to the invocation, not the committed file.--allow-file/--cdp— security-sensitive.--allow-fileunlocks thefile:gate and--cdpattaches to a running Chrome; a committed config must not be able to silently widen the trust boundary of every run. They stay explicit, per invocation. (They're also the triggers that suppress conflicting defaults — passing--cdpdrops the seededdevice/viewport/headful/storageState, and--mddropsformat— so a config default never trips a guard against a flag you actually typed.)
Legacy shorthands
Four top-level keys are accepted for back-compat — each folds into its canonical home:
pages— the former name forurls. Still parsed;urlswins if both are present.rules/failOn/deviceat the top level — shorthand for the same keys underdefaults.
Prefer the current forms; the shorthands may be removed before 1.0.
See also
@real-a11y-dev/cli— commands, flags, and exit codes.- Adopt the gate on existing debt — baselines.
- The CI diff bot —
snapshot+diffin a PR. - Authenticated pages —
storageStateand--cdp.