Accessibility
MicrosoftOfficialPublished by Microsoft
- Skill
- Tool integration
- Software development
- MIT
- Low risk
- Has scripts
AutoGineer summary
Written by AutoGineer from the skill’s own files, not by the author
The accessibility skill provides a structured approach to managing accessibility requirements across the development lifecycle. It includes a six-phase Accessibility Planner workflow, a command-line scanner for static analysis, and a runtime probe harness for verifying interaction-based accessibility. The skill enforces a doctrine of method adequacy, ensuring that verification methods are only used to decide criteria they are capable of observing, and supports the generation of evidence bundles for review.
What it does
- Provides a six-phase workflow for accessibility discovery, planning, and backlog handoff.
- Wraps the axe-core CLI to perform static accessibility scans on web pages.
- Executes Playwright-based runtime probes to verify interaction behavior and screen-reader announcements.
- Enforces method adequacy rules to distinguish between static findings and interaction-based verification.
- Generates deterministic evidence bundles for accessibility reviews and reporting.
- Maps findings to WCAG 2.2, ARIA Authoring Practices, and other accessibility standards.
When to use it
- When planning accessibility requirements for a new project or feature.
- When performing automated static scans of web content for common accessibility defects.
- When verifying complex interaction behaviors that static analysis cannot fully evaluate.
- When building an evidence register to track accessibility conformance across development phases.
Advantages
- Standardizes accessibility verification across different projects and teams.
- Prevents over-reliance on static analysis for complex interaction-based criteria.
- Supports tiered enforcement postures to match project risk and maturity.
- Integrates with standard CI workflows for automated accessibility testing.
Usage examples
- “Scan my local file index.html for accessibility violations.”
- The skill runs the axe-core scanner against the file and outputs a normalized JSON report of findings.
- “Run the accessibility runtime probes for my project.”
- The skill executes the configured Playwright probes and aggregates the results into a coverage matrix.
Install
1 of its files are not mirrored here, so install it from its source:
https://github.com/microsoft/hve-core/tree/f425e943bf228052a23e6bc4810b5faef030314f/.github/skills/accessibility/accessibility
Documentation by Microsoft
Accessibility — Skill Entry
This skill is the canonical accessibility reference contract for HVE Core. Agents and instructions invoke this skill by name and rely on it to own framework reference resolution, phase guidance resolution, and the scanner CLI entrypoint.
Framework references
Assessment reference (cross-cutting, not a conformance framework):
Assistive-technology announcement model — read this when deciding the announcement-class criteria (WCAG 1.3.1, 4.1.2, 4.1.3): what a screen-reader user should hear, and how to decide it with an accessibility-tree assertion or a manual AT pass.
Accessibility Planner workflow
The Accessibility Planner runs six phases, each keyed to a state id:
Phase 1 — Discovery (
discovery)Phase 2 — Framework Selection (
framework-selection)Phase 3 — Standards Mapping (
standards-mapping)Phase 4 — Plan Risk Assessment (
plan-risk-assessment)Phase 5 — Impact and Evidence (
impact-evidence)Phase 6 — Backlog Handoff (
backlog-handoff)
Phase reference index
Phase 1 — Discovery: capture-coaching.md — read this when running exploration-first capture questioning.
Phase 2 — Framework Selection: framework-selection.md — read this when choosing which frameworks and conformance level apply.
Phase 3 — Standards Mapping: walk the framework references roll-up tables to emit
controlMappings; consumed by Phase 5. No dedicated file — mapping is driven by the framework roll-ups.Phase 4 — Plan Risk Assessment: capture-coaching.md governs the questioning posture when escalation triggers reopen scoping; tier criteria are applied per the Accessibility Planner identity instructions and recorded as
riskClassification.tier. No dedicated file — the accessibility risk surface is narrow enough to stay inline.Phase 5 — Impact and Evidence: impact-assessment.md — read this when building the evidence register, tradeoff log, and seed work-items.
Phase 6 — Backlog Handoff: backlog-handoff.md — read this when rendering work items and validating handoff gates.
Method adequacy (decide vs inform)
Method adequacy is the doctrine that a verification method may only be recorded as satisfying a success criterion when that method can actually decide the criterion. A method that can observe a related signal but cannot confirm the user-facing outcome only informs the criterion; it raises or lowers suspicion but never closes it. This section is the canonical definition; the planner evidence register, the reviewer verdict logic, and the runtime probe harness all resolve adequacy through it.
The machine-readable adequacy source is scripts/runtime_a11y/probe-criteria-map.json. Each probe entry lists the criteria it decides and the criteria it only informs. A result counts as adequate only when the winning method appears in the decides list for that criterion and state.
Failure classes and adequate methods
Accessibility defects generalize into five classes. Static analysis (axe, eslint-plugin-jsx-a11y, snapshot structure counts) can decide only the first class; the remaining four require an interaction-state probe or an assistive-technology (AT) pass to be decided rather than merely informed.
| Class | Representative WCAG SC | What static analysis can do | Adequate method to decide |
|---|---|---|---|
| Static-decidable structure | 1.1.1, 1.4.3, 4.1.2 (name present), 2.4.2, 3.1.1 | Decide | axe / static scan |
| Interaction behavior | 2.1.1, 2.1.2, 2.4.3, 2.4.7, 2.4.11 | Inform only | Keyboard/interaction probe driving keys across focus/open states, or manual keyboard pass |
| Announcement correctness | 1.3.1, 4.1.2 (computed name/role), 4.1.3 | Inform only | Accessibility-tree assertion of computed name/role/live, or manual AT pass (Windows NVDA or human-led JAWS) |
| Adaptive rendering | 1.4.4, 1.4.10, 1.4.12, 2.4.11 | Inform only | Rendered probe at 200% zoom, 320px reflow, and text-spacing states |
| Faux semantics | 1.3.1 (faux headings), 2.4.3 (faux controls) | Cannot see | Heuristic source pass plus accessibility-tree assertion; no element exists for a rule engine to flag |
For the announcement class, the assistive-technology announcement model specifies what the user should hear per criterion and how to decide it. For the interaction and faux-semantics classes, the focus-management anti-pattern catalog enumerates the defects that pass a static scan yet break keyboard and screen-reader users.
Adequacy rule
A control in the interaction, announcement, adaptive-rendering, or faux-semantics classes cannot be recorded as fully satisfied on static evidence alone. Static evidence caps such a control at a partial state until an adequate method is attached.
A criterion whose winning method only
informsit is never a conformance verdict; it is a lead that routes to an adequate method or a manual pass.Preserve the adequacy determination alongside the status so downstream planners and reviewers do not re-promote an inadequately verified control.
This doctrine and its class taxonomy are repository-original content licensed under CC BY 4.0; the underlying success-criterion definitions remain with WCAG 2.2 as cited in wcag-22.md.
Calibration is evidence quality, not a conformance verdict
Calibration establishes that the assistive-technology stack behaves as expected before its output is trusted. Its checkpoints record driver identity, profile fingerprint, and artifact hashes for a journey that passed.
A checkpoint is deliberately not a matrix input. Nothing in the coverage or outcome pipeline reads one, and that separation is intended rather than unfinished. A passing calibration says the harness was working; it does not say the surface satisfies a success criterion. Wiring checkpoints into coverage would turn a statement about the tool into a statement about the product.
If calibration evidence should ever influence coverage, that is new design work requiring its own review: it needs an explicit mapping from a journey to a criterion, surface, and state, and it must respect the same method-adequacy rules as any other evidence.
Gate strictness by assessment tier
The enforcement posture for the interaction, announcement, adaptive-rendering, and faux-semantics classes graduates with the assessment depth tier recorded in riskClassification.tier, so the gate inherits the rigor a project opted into rather than applying one global switch:
basic— the adequate-method probes (keyboard, widget-keyboard, live-region, aria-tree, virtual-sr) report advisory (warn-only); the always-decidable core probes still block.standard(default) — ratchet: the adequate-method probes block on new or changed surfaces and stay advisory over the existing backlog, so no new defect in these classes ships while legacy content is surfaced without freezing the project.comprehensive— the adequate-method probes block on every in-scope surface.
The always-decidable core probes (axe, DOM hygiene, broken links, console errors, target size, contrast, reflow/resize) block at every tier. The CI workflow template implements this dial through the A11Y_TIER and A11Y_RATCHET_SURFACES inputs; the Accessibility Reviewer applies the same graduation to its FAIL and PARTIAL verdicts.
Tooling
The scanner CLI (scripts/scan.py) wraps the Node-based axe-core scanner and normalizes its findings into a stable JSON shape.
Prerequisites
Python 3.11+ with uv available on PATH.
Node.js with
npxavailable on PATH.Network access on first run so
npxcan fetch@axe-core/cli.
Quick Start
uv run scripts/scan.py https://example.com --allow-host example.com
uv run scripts/scan.py ./page.html --output results.json
uv run scripts/scan.py file:///path/to/page.htmlLoopback HTTP(S) targets need no additional option. Every other remote host
requires either a matching, repeatable --allow-host HOST value or the broader
--allow-external confirmation. Local paths and local file: URIs must resolve
to existing regular files. Network shares, remote file authorities, directories,
missing files, credentials, leading-dash targets, and non-HTTP(S) remote schemes
are rejected before the axe CLI starts.
Parameters Reference
| Parameter | Required | Default | Description |
|---|---|---|---|
target | Yes | — | Authorized HTTP(S) URL or existing regular local file to scan. |
--output | No | stdout | Path to write the normalized JSON results. |
--allow-host | No | none | Authorize one non-loopback HTTP(S) host; repeat as needed. |
--allow-external | No | false | Confirm access to any non-loopback HTTP(S) host. |
Remote authorization applies to the initial scanner target only. It does not constrain browser redirects, DNS address changes, subresources, WebSockets, Service Workers, downloads, or other browser-derived requests. Run the scanner only from a workstation whose network reach matches the target's trust level.
Script Reference
Entrypoint: scripts/scan.py
Output shape:
{ "target": "<scanned target>", "summary": { "violations": 0, "passes": 0, "incomplete": 0, "inapplicable": 0 }, "violations": [ { "id": "", "impact": "", "description": "", "nodes": 0 } ] }Exit codes:
0— scan completed successfully.1— scan failed or returned invalid output.2— scanner unavailable (Node.js or@axe-core/climissing).
Troubleshooting
| Symptom | Likely cause | Action | Exit code |
|---|---|---|---|
scanner unavailable error | Node.js or npx not on PATH | Install Node.js so npx resolves, then re-run. | 2 |
| Long pause or download on first run | npx is fetching @axe-core/cli | Allow network access on the first run; later runs use the cache. | — |
Refusing to probe non-loopback host | Remote host was not explicitly authorized | Add --allow-host HOST or confirm with --allow-external. | 2 |
Local scan target does not exist | Local path or file URI cannot be resolved | Supply an existing regular local file, not a directory or share. | 2 |
scan failed or returned invalid output | axe-core CLI errored or emitted non-JSON | Confirm the target URL or file is reachable and well-formed. | 1 |
Empty violations but issues expected | Page rendered after the scan, or rules N/A | Confirm the target fully loads; check summary.inapplicable. | 0 |
Mapping findings to frameworks
Each violation's impact is one of minor, moderate, serious, or critical. axe rule tags decode to WCAG success criteria by stripping the wcag prefix and inserting decimals:
| axe tag | WCAG success criterion |
|---|---|
wcag111 | 1.1.1 Non-text Content |
wcag143 | 1.4.3 Contrast (Minimum) |
WCAG success criteria are normative; the axe techniques that surface them are informative. Treat scanner output as evidence pointing at a criterion, not a conformance verdict.
Runtime probe harness
The runtime probe harness (scripts/runtime_a11y) runs Playwright-based accessibility probes against a project-specific surface inventory and aggregates the results into a coverage matrix. For workflow orchestration, ask the user to run the manual-only accessibility-coverage-matrix skill (/accessibility-coverage-matrix); it is not model-invocable. Use the Accessibility Surface Inventory subagent as the canonical producer of the runtime config. Activate it by name; when it does not resolve, warn the user that the capability is unavailable and stop the dependent step.
Harness prerequisites
Complete these once before any runtime-harness command. They are separate from the scan.py prerequisites above.
Python 3.11+ with uv available on PATH.
Node.js available on PATH, plus system Google Chrome, because the probes target
channel: 'chrome'.Skill-local Node dependencies installed under scripts/runtime_a11y. The CLI fails fast with an install hint when they are missing.
Automated Windows NVDA runs additionally require the manifest-selected Guidepup NVDA asset. Machine setup and asset installation are separate actions from
npm ci.
Install the harness dependencies from the scripts/runtime_a11y directory:
PLAYWRIGHT_SKIP_BROWSER_DOWNLOAD=1 npm ci$env:PLAYWRIGHT_SKIP_BROWSER_DOWNLOAD = '1'; npm ciPLAYWRIGHT_SKIP_BROWSER_DOWNLOAD=1 avoids downloading bundled browsers that the harness never uses.
For automated NVDA runs, prepare the Windows machine once, then install the asset selected by the skill-local Guidepup package. Run the asset command again after updating Guidepup:
Set-Location <skill-root>/scripts/runtime_a11y
npx --yes --registry=https://registry.npmjs.org/ @guidepup/[email protected] setup
npx --yes --registry=https://registry.npmjs.org/ @guidepup/[email protected] install nvdaThe commands configure the machine and write versioned assets to the Guidepup user cache. They are not part of ordinary dependency installation. Run the prerequisite-only probe before a calibration session:
uv run scripts/runtime_a11y/__main__.py run-calibration --config <path-to>/a11y-runtime.config.json --prerequisite-onlyThe preflight reports the skill-local library, manifest-selected NVDA asset, and any conflicting NVDA process independently. Do not start a personal NVDA instance before an automated run; the harness starts an isolated session and rejects an existing process.
Product-neutral screen-reader cases live in scripts/runtime_a11y/screen-reader-cases.json. A downstream binding supplies named routes, targets, states, and explicit execution recipes. Catalog digest drift, missing target closure, duplicate identities, unsupported commands, secret-bearing binding keys, and inferred actions fail before browser startup.
Use repeatable run-calibration --journey <id> arguments for an authorized live subset. Run the downstream integrity recipe before product cases and keep real NVDA advisory/manual. For deterministic evidence, use run-all --probe <probe-id> with surface/state filters or a reviewed probeScoping configuration; ordinary run-all must not be treated as permission to start a real screen reader.
Invocation
Run the harness through its script entrypoint. Invoke it from the skill root, which is the directory holding pyproject.toml, so uv resolves the skill's own environment. This matches the invocation convention used by scan.py and the other Python skills, and it needs no PYTHONPATH.
uv run scripts/runtime_a11y/__main__.py run-all --config a11y-runtime.config.json --out results.json
uv run scripts/runtime_a11y/__main__.py probe <probeId> --config a11y-runtime.config.json
uv run scripts/runtime_a11y/__main__.py render-artifacts --matrix coverage-matrix-repo.json --output-dir .copilot-tracking/accessibility/coverage --repo-slug repo
uv run scripts/runtime_a11y/__main__.py compose-evidence --helpTo run from any other working directory, pin the skill as the uv project and use the same script path:
uv run --project <skill-root> <skill-root>/scripts/runtime_a11y/__main__.py run-all --config a11y-runtime.config.json --out results.json--configresolves relative to the current working directory.--outwrites the aggregated JSON document to disk.--base-urloverrides the configured base URL. It must remain a loopback origin unless the host is allowlisted or--allow-externalis supplied.--tracecaptures Playwright traces and screenshots.--allow-externalconfirms intentional probing of a non-loopback host.render-artifactsturns a rendered matrix JSON document into the complete coverage evidence bundle.
Evidence bundle composition
compose-evidence combines downstream-owned catalogs, cadence scope, run context, normalized source envelopes, state proofs, and optional qualified-reviewer supplements into one deterministic evidence bundle. It is separate from render-artifacts: rendering creates coverage, EARL, and manual-plan views from a matrix, while composition creates a revision-bound trust envelope across evidence producers.
The command requires explicit input paths and never infers product scope, Git state, time, retention, branch policy, or a release verdict. A consuming project owns those decisions. The composer validates artifact bytes beneath --artifact-root, preserves conflicts and quarantined results, and always emits non-attestation metadata. Automation, reviewer evidence, and release evidence have separate completeness states.
For pull requests, a valid initial bundle may report reviewer evidence as pending. A qualified reviewer can later provide a privacy-minimized, digest-bound supplement and registry snapshot for deterministic recomposition against the prior bundle. Recomposition supplies both --prior-bundle and its independently retained --prior-bundle-digest; omission or mutation of prior history fails closed. Missing required reviewer evidence blocks release completeness, not ordinary bundle production. Raw screen-reader phrases and restricted transcript paths are forbidden in ordinary bundles; only approved counts, redacted metadata, and digests cross this boundary.
Use --require-completeness automated, reviewer, or release only when the downstream workflow intends that dimension to gate. Valid incomplete bundles are still written for diagnosis. The additional exits are:
| Exit code | Meaning |
|---|---|
5 | Automated evidence is incomplete. |
6 | Reviewer evidence is pending or invalid. |
7 | Release evidence is incomplete. |
Existing exits 0 through 4 retain their meanings. The JSON contracts live beside the runtime at evidence-bundle.schema.json, evidence-source.schema.json, evidence-scope.schema.json, screen-reader-method-cell.schema.json, and qualified-human-result.schema.json.
For a complete adopter path from an inactive template to a first retained bundle and deterministic recomposition, including schema-valid examples for every authored input, read references/evidence-composition-adoption.md.
Visual review capture
capture-visual-review records deterministic screenshot evidence for the configured surfaces and states. It requires visualReview.enabled to be true in the runtime config.
uv run scripts/runtime_a11y/__main__.py capture-visual-review \
--config <path-to>/a11y-runtime.config.json \
--run-root .copilot-tracking/accessibility/local-runs/<run-name>--run-rootselects the evidence directory. It resolves relative to the repository root and must land on a child path inside.copilot-tracking/accessibility/local-runs. Paths outside that root, and traversal segments, are rejected.Omitting
--run-rootallocates a timestamped run directory beneath that same root.--visual-surfaceand--visual-statenarrow the capture to specific configured ids.The server the command targets follows
serveMode. See Server modes.
Server modes
serveMode in the runtime config decides whether the harness manages a server for capture-visual-review.
serveMode | Behavior |
|---|---|
auto | Reuses a healthy server at the configured loopback origin, otherwise builds and starts one it owns and stops afterward. |
served | Requires a server already answering the configured loopback origin, and fails with the expected origin and start command when none responds. |
external | Never probes or starts a server. The configured target must already be reachable. |
off | Never probes or starts a server. |
auto and served accept only loopback origins, because the harness will not manage or health-probe a remote server. A non-loopback target still requires the allowlist or --allow-external, and is only valid with external or off.
Under auto, starting an owned server builds the site first, so a capture that has to start its own server takes several minutes before the first screenshot. Leave a server running at the configured origin to skip that cost, which is the reuse path.
The Docusaurus site in this repository is configured for auto at http://127.0.0.1:3001, which is the same production-build origin the Playwright end-to-end suite uses. Capturing against the production build keeps visual evidence consistent with what continuous integration verifies.
Config summary
The harness loads scripts/runtime_a11y/config-schema.json and expects a runtime config with fields such as baseUrl, serveMode, allowlist, routes, surfaces, and probeScoping. The config defines the surfaces and interaction states the probes execute. A runtime guard blocks non-loopback targets unless the host is allowlisted or the caller supplies --allow-external. serveMode controls server ownership for visual review capture, described under Server modes.
Real-NVDA journeys support three capture modes:
singlereads the cumulative speech log after commands and the configured settle interval.clear-and-captureclears earlier speech before the settle interval, then reads the cumulative log.actionwraps an existing post-start declarative trigger with Guidepup action capture. Assertions can selectactionSpeechoractionNormalizedSpeechto evaluate only output attributed to that trigger.
Action capture waits for Guidepup's one-second speech quiet window. It is appropriate for immediate interaction-driven announcements but does not extend to arbitrary delayed updates and cannot create speech that NVDA does not emit. Keep single, clear-and-capture, accessibility-tree evidence, and manual AT evidence available for those cases. Action mode fails closed when the journey lacks triggerAfterDriverStart, a declarative trigger, a real Guidepup driver, or action-capture support.
Probe inventory and adequacy map
The harness currently includes these probes under scripts/runtime_a11y/runner:
WCAG and ARIA APG probes:
probe-axeprobe-keyboard-traversalprobe-focus-visibleprobe-focus-obscuredprobe-live-regionprobe-aria-treeprobe-widget-keyboardprobe-reflow-resizeprobe-target-sizeprobe-contrastprobe-forced-colorsprobe-reduced-motionprobe-structure-crawlprobe-name-in-labelprobe-use-of-color(1.4.1)probe-text-spacing(1.4.12)probe-hover-focus(1.4.13)probe-link-purpose(2.4.4)probe-input-purpose(1.3.5)probe-forms(3.3.2, informs 3.3.1/3.3.3)probe-context-change(3.2.1, 3.2.2)probe-orientation(1.3.4)probe-audio-control(1.4.2)probe-timing(2.2.1)probe-zoom-blocker(1.4.4, informs 1.4.10)probe-virtual-sr(4.1.2 name/role announcement captured from a virtual screen reader's spoken-phrase log; informs 1.3.1)probe-real-sr(real Windows NVDA announcement assertions through a Guidepup adapter; only decides when configured expectations are present and the AT stack is available)
Non-WCAG defect-scan probes (framework defect-scan):
probe-console-errors(console/page errors)probe-broken-links(same-origin 404s)probe-dom-hygiene(duplicate ids, positive tabindex, missing/duplicate landmarks)probe-title-lang(empty title, invalidlang)
Method adequacy is encoded in scripts/runtime_a11y/probe-criteria-map.json. Each entry records which criteria a probe can decide and which criteria it can only inform. A result only counts as adequate when the winning method is allowed by that mapping.
Coverage engine outputs
The matrix engine in scripts/runtime_a11y/matrix expands the criterion x surface x state grid, merges updates deterministically, and preserves human-confirmed findings over lower-priority automation. It computes adequate-coverage percentages by framework and overall. The render-artifacts command emits a deterministic bundle containing coverage-matrix-{repo-slug}.json, coverage-matrix-{repo-slug}.md, accessibility-results-{repo-slug}.earl.jsonld, manual-at-testplan-{repo-slug}.md, manual-at-testplan-{repo-slug}.yaml, and accessibility-artifacts-{repo-slug}.json.
The EARL (Evaluation and Report Language) JSON-LD export is the normalized results contract for interoperability with the ACT Rules ecosystem and downstream VPAT or EAA review. Each evaluated cell becomes one stable earl:Assertion that records subject, criterion, state, method, method adequacy, result date, and evidence. A result whose winning method only informs its criterion is earl:cantTell, never a false earl:passed. Inapplicable cells are earl:inapplicable; unevaluated cells are omitted. The paired manual plans contain cells that still require a human-deciding method and provide blank result fields for evidence writeback.
Public ARIA-AT and execution contract
The catalog in scripts/runtime_a11y/aria-at-catalog.json is the repository's documented source of truth for generated AT-oriented cases. Its provenance fields are strict and immutable: each mapping records a local catalog version, a canonical upstream URL, a commit-pinned upstream URL, and the upstream commit SHA used for the reviewed refresh. The repository does not copy upstream assertion tables or prose; it paraphrases official W3C and ARIA-AT intent and cites the source in the generated artifacts.
The current public posture is intentionally conservative. The five starter defaults (modal dialog, checkbox, select-only combobox, menu button, and tabs) are citation-bearing manual-only mappings because the richer AT-mode and quick-navigation semantics that these patterns can require are not faithfully modeled by the current structured command boundary. Runtime overrides may create explicit synthetic contract tests, but those are always non-pass candidate evidence and never accessibility conformance evidence. The resolver uses the documented fallback order of state -> surface -> catalog; commands and assertions are stored as atomic arrays, and an explicit empty array intentionally disables execution for a given case.
Unknown patterns remain generic manual drafts or project-refinement markers rather than a fake automation pass. Equally specific ambiguity is treated as a configuration error before rendering or driver startup. The public CLI exposes render-artifacts as an optional mapping configuration step and run-at-plan as the supported path for listing, selecting, executing, and reporting generated AT cases. The real-driver boundary currently supports Guidepup-backed Windows NVDA automation plus manual-only JAWS and other operator-led flows; synthetic execution is a separate evidence channel and never claims a pass. The generated manual plans stay linked to the shared real screen reader testing runbook, while case-specific commands remain inside the generated plan output rather than being written back into the matrix or coverage artifacts.
The generated manual cases, synthetic or real execution evidence, ACT-style result reasoning, EARL outcomes, and later qualified-human review are distinct layers. The public workflow renders representative fixtures through the documented CLI and inspects the six generated artifacts, but it does not commit golden outputs for the inspection bundle.
Exit codes
0indicates the harness completed successfully, even when probes reported findings.Non-zero exit codes indicate a harness error such as invalid config, a failed probe, missing Node.js, uninstalled harness dependencies, missing browser support, or a blocked target.
verify-intent adds two design-intent codes that a consuming project's CI can distinguish:
| Exit code | Meaning | Typical response |
|---|---|---|
3 | A blocking expectation resolved failed | Treat as intent drift; the surface regressed |
4 | A blocking expectation was never evaluated | Treat as coverage drift; the check did not run |
The two are separate because a failed check and an unrun check call for different responses. Reusing one code would let missing coverage read as a regression, or pass silently.
Runtime dependencies
The harness resolves its Node dependencies from a skill-local package under scripts/runtime_a11y (package.json plus committed package-lock.json), pinning [email protected], @axe-core/[email protected], @guidepup/[email protected], and optional real-AT integration through @guidepup/[email protected]. Install the Node dependencies and the separately managed NVDA asset as described under Harness prerequisites. The probes then resolve their dependencies from the local node_modules; the CLI fails fast with an install hint when node_modules is absent, before it probes or starts any server.
Testing
The harness is tested in two tiers. Browserless verdict and pure-helper unit tests run under node --test (repo script test:node) and cover the decision logic in runner/_core.mjs (for example virtualSrNameRoleStatus and liveRegionStatus). Browser smoke tests under tests/runtime_a11y/runner/probe-smoke/ launch system Chrome and drive the real capture path against inline fixtures to prove the DOM-to-verdict pipeline, including nameless controls, live-region firing, forced-color computed styles, and CDP accessibility-node structure. They are named *.smoke.mjs so the default browserless run skips them. PR Validation runs them through the CI-owned npm run ci:test:a11y:smoke entry point. Local reproduction requires system Chrome and the skill-local node_modules.
CI regression gate
Use the ready-to-copy workflow template at references/ci/accessibility-coverage.workflow-template.yml as the documentation-first integration point for a target project. Copy it into the target repository's GitHub Actions workflow directory only after the target project commits an a11y-runtime.config.json and has a build/serve path that the template can invoke. Once authored *.intent.yaml records exist, the template fails closed if the config or current-run results are missing.
The template mirrors the Docusaurus workflow recipe by provisioning system Chrome, setting up Node 24 plus Python and uv, building the target, serving it under a configurable base URL, and running the harness script entrypoint with uv run --project pinned at the vendored skill root. The core high-confidence probes always block: probe-axe, probe-dom-hygiene, probe-broken-links, probe-console-errors, probe-target-size, probe-contrast, and probe-reflow-resize. The interaction-state and announcement probes (probe-keyboard-traversal, probe-widget-keyboard, probe-live-region, probe-aria-tree, probe-virtual-sr, probe-real-sr) are the adequate method for the classes static analysis only informs, and their blocking posture follows the A11Y_TIER dial defined under Gate strictness by assessment tier: basic reports them advisory, standard ratchets (blocking on the surfaces listed in A11Y_RATCHET_SURFACES), and comprehensive blocks them everywhere. The remaining heuristic probes such as use-of-color, hover-focus, link-purpose, name-in-label, and focus-* are surfaced as informational results so they can guide follow-up work without blocking initial adoption. The real-screen-reader probe stays advisory by default unless a project opts into it through configured expected assertions and a supported OS/AT stack; it returns candidate when the platform or AT is unavailable rather than pretending a pass or failure. Decisive coverage of the adaptive-rendering class depends on the target committing the zoom-200, reflow-320, and text-spacing states in its a11y-runtime.config.json.
The parity reference at references/ci/probe-spec-parity.md maps each runtime probe to the closest existing Docusaurus e2e spec and highlights gaps where no equivalent spec currently exists.
The references/ci/act-rule-format.md reference documents how the harness's probes, generated test cases, and EARL results align to the W3C ACT Rules Format (applicability, expectation, passed/failed/inapplicable) so results interoperate with the ACT Rules ecosystem and conformance reporting.
Design intent verification
A consuming project can declare what a surface must convey and have CI check that what shipped still matches. The declaration is a Design Intent Record, human-authored and committed at design-intent/<surface-id>.intent.yaml in that project. Each intent states what the surface must communicate, why, and for whom, and binds every claim to a named check.
The authored-record field contract is in references/design-intent/record-contract.md. Read it when authoring or reviewing a record. This section covers verification.
This skill supplies the step that turns a probe run into a verdict against that declaration.
Invocation
uv run --project <skill-root> <skill-root>/scripts/runtime_a11y/__main__.py verify-intent \
--record design-intent/<surface-id>.intent.yaml \
--results results.json--recordis the authored record. It is never read for anything but its declarations and never rewritten.--resultsis a results document fromrun-all.--outoverrides the output path, which defaults todesign-intent/.verification/<surface-id>.earl.jsonbeside the record.
How results resolve to outcomes
A result row matches an expectation when its surface, state, emitting probe, and criterion all match what the record declares. Because criterion coverage overlaps across probes, the emitting probe is part of the join.
| Situation | Outcome | Mode |
|---|---|---|
| Every matched criterion passed | passed | automatic |
| Any matched criterion failed | failed | automatic |
| Evidence gathered but not decisive | cantTell | automatic |
| No result row matched the expectation | untested | automatic |
The expectation uses assert: custom | untested | manual |
An expectation is one claim over one or more criteria, so the worst criterion outcome governs. Expectations the run did not cover report untested rather than being omitted, which keeps a missing check visible instead of silently absent.
Expectations whose criteria a probe only informs resolve to cantTell in normal operation. That is expected: the probe gathered evidence without settling the claim.
A custom expectation reports untested whether or not the record carries a human override. The adapter records only observed runtime outcome in the artifact. Human override remains authoritative in the record itself and is not merged into the generated artifact outcome.
Interpretation is two-layered by contract, and the generated artifact now carries both layers on every assertion:
observedOutcomeis the generated artifact fieldoutcome. An override never replaces it.effectiveOutcomeapplies fail-safe precedence. Either an observed or humanfailedoutcome yieldsfailed; humanpassedsettles onlyuntested,cantTell, orinapplicable; otherwise the observed outcome remains. This is the value the gate applies.overrideConflictistrueonly when the observed and authored override outcomes are eachpassedorfailedand differ. An override that settles anuntested,cantTell, orinapplicableexpectation is the documented use, not a conflict.
A conclusive conflict fails closed. verify-intent writes one Warning: line to stderr for each conflicting assertion, names the intent, expectation, observed outcome, and override outcome, and returns design-intent drift when the conflicting expectation blocks.
The shipped Python verifier rejects duplicate YAML keys, validates the complete authored schema, and enforces semantic checks for dates, filename and runtime-config bindings, duplicate identifiers, method pairing, and probe adequacy before writing an artifact. This repository's PowerShell validator independently enforces the same contract for source and generated-artifact validation.
Exit codes
0— the artifact was written and no blocking expectation failed.1— the record or results document was malformed. Nothing is written.2— the record or results document could not be read.3— the artifact was written and a blocking expectation failed.4— the artifact was written and a blocking expectation was never evaluated.
Exit codes 3 and 4 are enforcement signals. A consuming project's CI step fails its build on either non-zero exit, exactly as it would for any other command. The artifact is still written so the run can publish it. Which expectations block is the record's decision, through each expectation's blocking flag.
Graphics and diagram semantics have no runtime probe. Those expectations use assert: custom and resolve through human review; see the Graphics ARIA and SVG-AAM reference for the boundary between what automation can decide and what it cannot.
Rendering a record for review
A record states what must be true and how it is checked, but carries no implementation or delivery scope. Reviewers who want prose rather than YAML can render one:
uv run --project <skill-root> <skill-root>/scripts/runtime_a11y/__main__.py project-intent \
--record design-intent/<surface-id>.intent.yaml \
--out handoff.mdOutput is deterministic and renders only what the record already says, adding no analysis and no verification results. Generate it on demand rather than committing it, so it cannot drift from the record it describes. Without --out the Markdown goes to stdout.
Usage notes
Treat this skill as the default accessibility entrypoint for planning and review workflows.
Resolve framework and phase guidance through this skill instead of duplicating its internal reference paths in agents or instructions.
Use the scanner CLI when you need normalized findings from an accessibility scan.
Files
- ATTRIBUTION.mdAdded by AutoGineer836 B
- LICENSELicence file added by AutoGineer1.1 KiB
- SECURITY.md40.9 KiB
- SKILL.md41.5 KiB
- pyproject.toml891 B
- references/ci/accessibility-coverage.workflow-template.yml23.3 KiB
- references/ci/act-rule-format.md5.4 KiB
- references/ci/probe-spec-parity.md15.3 KiB
- references/design-intent/record-contract.md9.6 KiB
- references/evidence-composition-adoption.md8.6 KiB
- references/examples/evidence/asset-journeys.json364 B
- references/examples/evidence/evidence-scope.json396 B
- references/examples/evidence/requirement-methods.json683 B
- references/examples/evidence/run-context.json994 B
- references/examples/evidence/state-proofs.json367 B
- references/frameworks/aria-apg.md88.7 KiB
- references/frameworks/at-announcement-model.md12.5 KiB
- references/frameworks/coga.md81.0 KiB
- references/frameworks/en-301-549.md153.9 KiB
- references/frameworks/graphics-aria-svg-aam.md6.9 KiB
- references/frameworks/section-508.md36.7 KiB
- references/frameworks/wcag-22.md66.2 KiB
- references/phases/backlog-handoff.md26.2 KiB
- references/phases/capture-coaching.md11.4 KiB
- references/phases/framework-selection.md8.3 KiB
- references/phases/impact-assessment.md18.8 KiB
- scripts/runtime_a11y/.npmrc923 B
- scripts/runtime_a11y/__init__.pyScript154 B
- scripts/runtime_a11y/__main__.pyScript89.3 KiB
- scripts/runtime_a11y/_config.pyScript8.1 KiB
- scripts/runtime_a11y/_errors.pyScript1.3 KiB
- scripts/runtime_a11y/_intent.pyScript33.0 KiB
- scripts/runtime_a11y/_projection.pyScript6.9 KiB
- scripts/runtime_a11y/aria-at-catalog.json20.7 KiB
- scripts/runtime_a11y/aria-at-catalog.schema.json5.7 KiB
- scripts/runtime_a11y/composition-summary.schema.json655 B
- scripts/runtime_a11y/config-schema.json12.1 KiB
- scripts/runtime_a11y/criteria-catalog.aria-apg.json1.6 KiB
- scripts/runtime_a11y/criteria-catalog.defect-scan.json2.0 KiB
- scripts/runtime_a11y/criteria-catalog.schema.json5.1 KiB
- scripts/runtime_a11y/criteria-catalog.wcag-22.json9.6 KiB
- scripts/runtime_a11y/design-intent.schema.json10.5 KiB
- scripts/runtime_a11y/evidence-bundle.schema.json11.2 KiB
- scripts/runtime_a11y/evidence-scope.schema.json2.2 KiB
- scripts/runtime_a11y/evidence-source.schema.json3.7 KiB
- scripts/runtime_a11y/evidence_bundle/__init__.pyScript494 B
- scripts/runtime_a11y/evidence_bundle/_adapters.pyScript15.1 KiB
- scripts/runtime_a11y/evidence_bundle/_canonical.pyScript1.3 KiB
- scripts/runtime_a11y/evidence_bundle/_compose.pyScript32.0 KiB
- scripts/runtime_a11y/evidence_bundle/_validate.pyScript5.8 KiB
- scripts/runtime_a11y/matrix/__init__.pyScript643 B
- scripts/runtime_a11y/matrix/_aria_at.pyScript10.7 KiB
- scripts/runtime_a11y/matrix/_artifacts.pyScript6.6 KiB
- scripts/runtime_a11y/matrix/_build.pyScript1.6 KiB
- scripts/runtime_a11y/matrix/_catalog.pyScript7.3 KiB
- scripts/runtime_a11y/matrix/_coverage.pyScript3.0 KiB
- scripts/runtime_a11y/matrix/_ingest_assessor.pyScript4.4 KiB
- scripts/runtime_a11y/matrix/_ingest_planner.pyScript5.6 KiB
- scripts/runtime_a11y/matrix/_ingest_probe.pyScript2.7 KiB
- scripts/runtime_a11y/matrix/_ingest_reports.pyScript3.5 KiB
- scripts/runtime_a11y/matrix/_merge.pyScript3.0 KiB
- scripts/runtime_a11y/matrix/_model.pyScript5.8 KiB
- scripts/runtime_a11y/matrix/_provenance.pyScript4.6 KiB
- scripts/runtime_a11y/matrix/_render_earl.pyScript7.3 KiB
- scripts/runtime_a11y/matrix/_render_json.pyScript2.1 KiB
- scripts/runtime_a11y/matrix/_render_md.pyScript4.5 KiB
- scripts/runtime_a11y/matrix/_render_test_plan.pyScript19.1 KiB
- scripts/runtime_a11y/methods.json1.8 KiB
- scripts/runtime_a11y/package-lock.json12.3 KiB
- scripts/runtime_a11y/package.json554 B
- scripts/runtime_a11y/probe-criteria-map.json11.9 KiB
- scripts/runtime_a11y/qualified-human-result.schema.json4.0 KiB
- scripts/runtime_a11y/runner/_core.mjsScript12.5 KiB
- scripts/runtime_a11y/runner/_shared.mjsScript42.4 KiB
- scripts/runtime_a11y/runner/assertions.mjsScript10.4 KiB
- scripts/runtime_a11y/runner/at-plan-executor.mjsScript40.0 KiB
- scripts/runtime_a11y/runner/calibration-checkpoint.mjsScript2.0 KiB
- scripts/runtime_a11y/runner/calibration-executor.mjsScript51.5 KiB
- scripts/runtime_a11y/runner/case-catalog.mjsScript13.0 KiB
- scripts/runtime_a11y/runner/drivers/command-contract.mjsScript4.2 KiB
- scripts/runtime_a11y/runner/drivers/driver-contract.mjsScript5.4 KiB
- scripts/runtime_a11y/runner/drivers/guidepup-adapter.mjsScript16.0 KiB
- scripts/runtime_a11y/runner/index.mjsScript894 B
- scripts/runtime_a11y/runner/methods.mjsScript1.0 KiB
- scripts/runtime_a11y/runner/probe-aria-tree.mjsScript2.1 KiB
- scripts/runtime_a11y/runner/probe-audio-control.mjsScript1.4 KiB
- scripts/runtime_a11y/runner/probe-axe.mjsScript2.7 KiB
- scripts/runtime_a11y/runner/probe-broken-links.mjsScript4.4 KiB
- scripts/runtime_a11y/runner/probe-console-errors.mjsScript1.7 KiB
- scripts/runtime_a11y/runner/probe-context-change.mjsScript2.5 KiB
- scripts/runtime_a11y/runner/probe-contrast.mjsScript1.8 KiB
- scripts/runtime_a11y/runner/probe-dom-hygiene.mjsScript3.4 KiB
- scripts/runtime_a11y/runner/probe-focus-obscured.mjs2.3 KiB
- scripts/runtime_a11y/runner/probe-focus-visible.mjs1.7 KiB
- scripts/runtime_a11y/runner/probe-forced-colors.mjs2.5 KiB
- scripts/runtime_a11y/runner/probe-forms.mjs2.2 KiB
- scripts/runtime_a11y/runner/probe-hover-focus.mjs2.2 KiB
- scripts/runtime_a11y/runner/probe-input-purpose.mjs2.1 KiB
- scripts/runtime_a11y/runner/probe-keyboard-traversal.mjs2.8 KiB
- scripts/runtime_a11y/runner/probe-link-purpose.mjs2.1 KiB
- scripts/runtime_a11y/runner/probe-live-region.mjs1.4 KiB
- scripts/runtime_a11y/runner/probe-name-in-label.mjs1.5 KiB
- scripts/runtime_a11y/runner/probe-orientation.mjs1.6 KiB
- scripts/runtime_a11y/runner/probe-real-sr.mjs2.0 KiB
- scripts/runtime_a11y/runner/probe-reduced-motion.mjs1.4 KiB
- scripts/runtime_a11y/runner/probe-reflow-resize.mjs1.1 KiB
- scripts/runtime_a11y/runner/probe-structure-crawl.mjs2.0 KiB
- scripts/runtime_a11y/runner/probe-target-size.mjs4.9 KiB
- scripts/runtime_a11y/runner/probe-text-spacing.mjs1.8 KiB
- scripts/runtime_a11y/runner/probe-timing.mjs1.2 KiB
- scripts/runtime_a11y/runner/probe-title-lang.mjs1.9 KiB
- scripts/runtime_a11y/runner/probe-use-of-color.mjs2.4 KiB
- scripts/runtime_a11y/runner/probe-virtual-sr.mjs1.2 KiB
- scripts/runtime_a11y/runner/probe-widget-keyboard.mjs2.1 KiB
- scripts/runtime_a11y/runner/probe-zoom-blocker.mjs1.6 KiB
- scripts/runtime_a11y/runner/route.mjs2.3 KiB
- scripts/runtime_a11y/runner/transcript.mjs2.1 KiB
- scripts/runtime_a11y/runner/validation.mjs1.8 KiB
- scripts/runtime_a11y/runner/visual-review-executor.mjs14.4 KiB
- scripts/runtime_a11y/schema-validation-report.schema.json826 B
- scripts/runtime_a11y/screen-reader-binding.schema.json3.5 KiB
- scripts/runtime_a11y/screen-reader-case.schema.json1.6 KiB
- scripts/runtime_a11y/screen-reader-cases.json6.8 KiB
- scripts/runtime_a11y/screen-reader-method-cell.schema.json2.2 KiB
- scripts/runtime_a11y/validation-manifest.schema.json3.8 KiB
- scripts/runtime_a11y/validation_manifest.py7.1 KiB
- scripts/runtime_a11y/visual-review-manifest.schema.json3.2 KiB
- scripts/runtime_a11y/visual_review.py20.5 KiB
- scripts/scan.py10.9 KiB
- scripts/scanner_npm/.npmrc344 B
- scripts/scanner_npm/package.json203 B
- tests/conftest.py1.4 KiB
- tests/corpus/0_empty(opens on GitHub)Not mirrored: opens on GitHub5 B
- tests/corpus/1_axe_violation91 B
- tests/corpus/2_selector_dense85 B
- tests/corpus/3_empty_fields77 B
- tests/corpus/4_json_like_payload87 B
- tests/fixtures/combobox.html329 B
- tests/fixtures/contrast.html194 B
- tests/fixtures/dialog.html264 B
- tests/fixtures/headings.html182 B
- tests/fuzz_harness.py4.7 KiB
- tests/runtime_a11y/fixtures/aria-at-driver-matrix.json3.7 KiB
- tests/runtime_a11y/fixtures/aria-at-modal-dialog-matrix.json516 B
- tests/runtime_a11y/fixtures/assessor_findings.md330 B
- tests/runtime_a11y/fixtures/planner_state.json464 B
- tests/runtime_a11y/fixtures/probe_results.json329 B
- tests/runtime_a11y/fixtures/report_findings.md443 B
- tests/runtime_a11y/runner/_core.test.mjs10.8 KiB
- tests/runtime_a11y/runner/_shared.test.mjs13.3 KiB
- tests/runtime_a11y/runner/assertions-structured.test.mjs3.5 KiB
- tests/runtime_a11y/runner/at-plan-executor.test.mjs74.3 KiB
- tests/runtime_a11y/runner/calibration-checkpoint.test.mjs3.2 KiB
- tests/runtime_a11y/runner/calibration-executor.test.mjs58.0 KiB
- tests/runtime_a11y/runner/calibration-fixtures.mjs1.5 KiB
- tests/runtime_a11y/runner/case-catalog.test.mjs14.0 KiB
- tests/runtime_a11y/runner/cleanup-ownership.test.mjs2.4 KiB
- tests/runtime_a11y/runner/command-contract.test.mjs4.1 KiB
- tests/runtime_a11y/runner/methods.test.mjs2.2 KiB
- tests/runtime_a11y/runner/probe-broken-links.test.mjs4.7 KiB
- tests/runtime_a11y/runner/probe-smoke/live-region.cases.mjs1.5 KiB
- tests/runtime_a11y/runner/probe-smoke/probe-aria-tree.smoke.mjs2.1 KiB
- tests/runtime_a11y/runner/probe-smoke/probe-forced-colors.smoke.mjs2.6 KiB
- tests/runtime_a11y/runner/probe-smoke/probe-live-region.smoke.mjs1.9 KiB
- tests/runtime_a11y/runner/probe-smoke/probe-virtual-sr.smoke.mjs1.6 KiB
- tests/runtime_a11y/runner/probe-smoke/virtual-sr.cases.mjs998 B
- tests/runtime_a11y/runner/probe-smoke/visual-review.smoke.mjs2.8 KiB
- tests/runtime_a11y/runner/probe-verdicts.test.mjs4.5 KiB
- tests/runtime_a11y/runner/real-sr.test.mjs30.3 KiB
- tests/runtime_a11y/runner/route.test.mjs2.3 KiB
- tests/runtime_a11y/runner/visual-review-executor.test.mjs7.9 KiB
- tests/runtime_a11y/test_aria_at.py20.4 KiB
- tests/runtime_a11y/test_aria_at_dogfood.py4.6 KiB
- tests/runtime_a11y/test_artifacts.py17.5 KiB
- tests/runtime_a11y/test_branch_completion.py5.7 KiB
- tests/runtime_a11y/test_build_coverage.py2.6 KiB
- tests/runtime_a11y/test_catalog.py3.7 KiB
- tests/runtime_a11y/test_cli.py95.0 KiB
- tests/runtime_a11y/test_cli_branches.py26.8 KiB
- tests/runtime_a11y/test_config.py11.3 KiB
- tests/runtime_a11y/test_config_guard.py3.4 KiB
- tests/runtime_a11y/test_coverage.py1.3 KiB
- tests/runtime_a11y/test_evidence_bundle.py43.0 KiB
- tests/runtime_a11y/test_ingest.py1.7 KiB
- tests/runtime_a11y/test_ingest_branches.py5.0 KiB
- tests/runtime_a11y/test_ingest_reports.py2.6 KiB
- tests/runtime_a11y/test_intent.py51.6 KiB
- tests/runtime_a11y/test_live_lifecycle.py8.9 KiB
- tests/runtime_a11y/test_merge.py2.1 KiB
- tests/runtime_a11y/test_merge_branches.py3.0 KiB
- tests/runtime_a11y/test_projection.py12.3 KiB
- tests/runtime_a11y/test_provenance.py4.8 KiB
- tests/runtime_a11y/test_render.py4.0 KiB
- tests/runtime_a11y/test_render_earl.py5.5 KiB
- tests/runtime_a11y/test_sample_bundle.py7.7 KiB
- tests/runtime_a11y/test_validation_manifest.py9.1 KiB
- tests/runtime_a11y/test_visual_review.py21.0 KiB
- tests/test_scan.py10.2 KiB
- tests/test_scan_branches.py4.6 KiB
- uv.lock114.8 KiB
references/frameworks/aria-apg.md
title: ARIA Authoring Practices Guide framework reference description: WAI-ARIA Authoring Practices Guide widget patterns used as an accessibility assessment knowledge base by the Accessibility Planner and Skill Assessor subagent
ARIA Authoring Practices Guide framework reference
This skill packages the W3C WAI-ARIA Authoring Practices Guide (APG) as an accessibility assessment knowledge base. APG documents the keyboard interaction, ARIA roles, states, and properties for the common interactive widget patterns that web authors implement. The skill groups 44 design patterns into seven families (Disclosure, Combobox, Grid, Menu, Tabs, Treegrid, and Dialog) and points the Accessibility Skill Assessor subagent at the per-family reference files in references/ when a finding involves a specific pattern.
APG is non-normative implementation guidance that operationalises the normative WAI-ARIA specification, so APG patterns sit alongside the wcag-22 skill: APG describes how to build the widget, WCAG 2.2 describes which behaviours the finished widget must satisfy. Assessor subagents typically cite both — the APG pattern for the widget contract and the relevant WCAG 2.2 success criterion for the user-facing requirement.
Source: W3C WAI-ARIA Authoring Practices Guide, https://www.w3.org/WAI/ARIA/apg/. APG content is published under the W3C Document License. Per the repository licensing posture in accessibility-license-posture.instructions.md, pattern summaries in this skill are paraphrased in the authors' own words and every reference file links to the canonical W3C pattern URL for verification. When that instruction content is unavailable, apply the paraphrase-only posture stated here and do not quote APG content verbatim.
Pattern roll-up
| Pattern | Family | Primary roles | Required keyboard | Required ARIA | Reference |
|---|---|---|---|---|---|
| Accordion | Disclosure | button, region | Enter, Space, Down/Up arrows (optional), Home, End | aria-expanded, aria-controls | family-disclosure.md#pattern-accordion |
| Disclosure (Show/Hide) | Disclosure | button | Enter, Space | aria-expanded, aria-controls (optional) | family-disclosure.md#pattern-disclosure-show-hide |
| Carousel (Auto-Rotating) | Disclosure | region, button | Left/Right arrows, Space or Enter (pause) | aria-live, aria-controls | family-disclosure.md#pattern-carousel-auto-rotating |
| Carousel (Tabbed) | Disclosure | tablist, tab, tabpanel | Left/Right arrows, Enter, Space | aria-selected | family-disclosure.md#pattern-carousel-tabbed |
| Disclosure Navigation | Disclosure | button, navigation | Enter, Space, Escape (optional), Tab | aria-expanded | family-disclosure.md#pattern-disclosure-navigation |
| Disclosure Navigation Hybrid | Disclosure | button, link | Enter, Space, Tab, arrow keys (optional) | aria-expanded, aria-haspopup (optional) | family-disclosure.md#pattern-disclosure-navigation-hybrid |
| Combobox (Autocomplete Both) | Combobox | combobox, listbox, option | Down/Up arrows, Escape, Enter, Alt+Down or Alt+Up | aria-expanded, aria-controls, aria-autocomplete="both", aria-activedescendant | family-combobox.md#pattern-combobox-autocomplete-both |
| Combobox (List) | Combobox | combobox, listbox, option | Down/Up arrows, Escape, Enter, Backspace | aria-expanded, aria-controls, aria-autocomplete="list", aria-activedescendant | family-combobox.md#pattern-combobox-list |
| Combobox (None) | Combobox | combobox, listbox, option | Down arrow, Enter, Escape | aria-expanded, aria-controls, aria-autocomplete="none", aria-activedescendant | family-combobox.md#pattern-combobox-none |
| Combobox (Select-Only) | Combobox | combobox, listbox, option | Down/Up arrows, Escape, Enter, Space, printable characters | aria-expanded, aria-controls, aria-activedescendant | family-combobox.md#pattern-combobox-select-only |
| Combobox (Grid Popup) | Combobox | combobox, grid, gridcell | Arrow keys, Home, End, Enter, Escape | aria-haspopup="grid", aria-expanded, aria-controls, aria-activedescendant | family-combobox.md#pattern-combobox-grid-popup |
| Listbox | Combobox | listbox, option | Down/Up arrows, Space, Shift+Space, Home, End, Ctrl+A | aria-selected, aria-multiselectable, aria-disabled | family-combobox.md#pattern-listbox |
| Grid (Layout) | Grid | grid, row, gridcell | Arrow keys, Home, End, Ctrl+Home, Ctrl+End, Enter or Space | aria-selected | family-grid.md#pattern-grid-layout |
| Grid (Data, Single-Cell Selection) | Grid | grid, row, gridcell, columnheader | Arrow keys, Home, End, Ctrl+Home, Ctrl+End, Enter or Space | aria-selected, aria-readonly | family-grid.md#pattern-grid-data-single-cell |
| Grid (Data, Multi-Cell Selection) | Grid | grid, row, gridcell | Arrow keys, Ctrl+Space, Shift+arrow keys, Ctrl+A | aria-multiselectable, aria-selected | family-grid.md#pattern-grid-data-multi-cell |
| Spinbutton | Grid | spinbutton | Up/Down arrows, Page Up, Page Down, Home, End | aria-valuenow, aria-valuemin, aria-valuemax, aria-valuetext, aria-disabled | family-grid.md#pattern-spinbutton |
| Slider (Single-Thumb) | Grid | slider | Left/Right or Up/Down arrows, Home, End, Page Up, Page Down | aria-valuenow, aria-valuemin, aria-valuemax, aria-valuetext, aria-orientation, aria-disabled | family-grid.md#pattern-slider-single-thumb |
| Slider (Two-Thumb) | Grid | slider | Arrow keys, Tab between thumbs, Home, End | aria-valuenow, aria-valuemin, aria-valuemax, aria-valuetext, aria-label, aria-orientation | family-grid.md#pattern-slider-two-thumb |
| Menu Button | Menu | button, menu, menuitem | Enter, Space, Down/Up arrows, Escape, Tab, printable characters | aria-haspopup="menu", aria-expanded, aria-controls | family-menu.md#pattern-menu-button |
| Menubar (Editor) | Menu | menubar, menuitem | Tab, arrow keys, Down (open menu), Right (next menu), Enter, Space, Escape | aria-haspopup="menu", aria-expanded, aria-orientation | family-menu.md#pattern-menubar-editor |
| Menubar (Navigation) | Menu | menubar, menuitem, link | Tab, Left/Right arrows, Down (submenu), Up, Escape, Enter, Space | aria-haspopup, aria-orientation | family-menu.md#pattern-menubar-navigation |
| Actions Menu Button | Menu | button, menu, menuitem | Enter, Space, Down arrow, arrow keys, Escape, printable characters | aria-haspopup="menu", aria-expanded | family-menu.md#pattern-actions-menu-button |
| Menubar with Multiple Selection | Menu | menubar, menuitemcheckbox, menuitemradio | Arrow keys, Space, Enter, Shift+arrow (optional), Ctrl+A (optional) | aria-checked, aria-multiselectable | family-menu.md#pattern-menubar-multi-select |
| Tabs (Manual Activation) | Tabs | tablist, tab, tabpanel | Tab, Left/Right or Up/Down arrows, Home, End, Enter, Space, Delete (optional) | aria-selected, aria-controls, aria-labelledby, aria-orientation | family-tabs.md#pattern-tabs-manual-activation |
| Tabs (Automatic Activation) | Tabs | tablist, tab, tabpanel | Tab, Left/Right or Up/Down arrows, Home, End, Delete (optional) | aria-selected, aria-controls, aria-labelledby, aria-orientation | family-tabs.md#pattern-tabs-automatic-activation |
| Tree View | Treegrid | tree, treeitem | Arrow keys, Home, End, asterisk (optional), printable characters | aria-expanded, aria-level, aria-posinset, aria-setsize, aria-selected | family-treegrid.md#pattern-tree-view |
| Treegrid (Email Client) | Treegrid | treegrid, row, gridcell | Right/Left arrows (expand/collapse), Down/Up arrows, Home, End, Ctrl+Home, Ctrl+End, Space | aria-expanded, aria-level, aria-selected, aria-controls | family-treegrid.md#pattern-treegrid-email-client |
| Link | Treegrid | link (native <a> or role) | Enter, Space (optional with role), Tab | aria-current, aria-disabled, aria-label, aria-expanded (optional) | family-treegrid.md#pattern-link |
| Alert | Dialog | alert | None (announced on insertion); Tab through embedded controls | aria-live="assertive", aria-atomic="true" | family-dialog.md#pattern-alert |
| Alert Dialog (Modal) | Dialog | alertdialog | Tab, Shift+Tab, Escape, Enter on default button | aria-modal="true", aria-labelledby, aria-describedby | family-dialog.md#pattern-alert-dialog-modal |
| Dialog (Modal) | Dialog | dialog | Tab, Shift+Tab, Escape, Enter on submit | aria-modal="true", aria-labelledby, aria-describedby | family-dialog.md#pattern-dialog-modal |
| Feed | Dialog | feed, article | Page Up, Page Down, Home, End (optional), Tab | aria-label, aria-live="polite", aria-relevant, aria-busy | family-dialog.md#pattern-feed |
| Breadcrumb | Dialog | navigation, link | Tab, Enter | aria-label, aria-current="page" | family-dialog.md#pattern-breadcrumb |
| Notification (Live Region) | Dialog | alert, status, log | None (announced); Tab through embedded controls | aria-live, aria-atomic, aria-relevant | family-dialog.md#pattern-notification-live-region |
| Switch | Dialog | switch | Space, Enter (optional), Tab | aria-checked, aria-label, aria-disabled | family-dialog.md#pattern-switch |
| Radio Group | Dialog | radiogroup, radio | Tab, Space, arrow keys | aria-checked, aria-label | family-dialog.md#pattern-radio-group |
| Radio Group (Roving Tabindex) | Dialog | radiogroup, radio | Tab, arrow keys, Space (optional) | aria-checked, managed tabindex | family-dialog.md#pattern-radio-group-roving-tabindex |
| Checkbox (Dual State) | Dialog | checkbox | Space, Tab | aria-checked (true or false), aria-label, aria-disabled | family-dialog.md#pattern-checkbox-dual-state |
| Checkbox (Mixed State) | Dialog | checkbox | Space, Tab | aria-checked="mixed" (plus true and false), aria-label, aria-disabled | family-dialog.md#pattern-checkbox-mixed-state |
| Button | Dialog | button | Enter, Space, Tab, Shift+F10 (optional) | aria-pressed, aria-haspopup, aria-expanded, aria-disabled, aria-label | family-dialog.md#pattern-button |
| Meter | Dialog | meter | None (read-only display) | aria-valuenow, aria-valuemin, aria-valuemax, aria-valuetext, aria-label | family-dialog.md#pattern-meter |
| Tooltip | Dialog | tooltip | Tab triggers display, Escape (optional) | aria-describedby on trigger, aria-expanded (optional) | family-dialog.md#pattern-tooltip |
| Window Splitter | Dialog | separator | Tab, arrow keys, Home, End, Enter (optional) | aria-valuenow, aria-valuemin, aria-valuemax, aria-orientation, aria-label, aria-controls | family-dialog.md#pattern-window-splitter |
| Landmarks | Dialog | navigation, main, complementary, contentinfo, search, region, form, application | None (structural; screen-reader landmark navigation) | aria-label, aria-labelledby, aria-current | family-dialog.md#pattern-landmarks |
Total: 44 patterns across 7 families (Disclosure: 6; Combobox: 6; Grid: 6; Menu: 5; Tabs: 2; Treegrid: 3; Dialog: 16).
Assessment heuristics
Per-pattern keyboard interaction lists, ARIA role and state requirements, and pattern-specific notes live inside the per-family reference files in references/. The Accessibility Skill Assessor subagent consumes the appropriate family-<name>.md#pattern-<slug> section when evaluating a finding against a specific APG pattern.
Cross-skill use is the common case: an APG finding usually cites both the pattern reference here and the relevant WCAG 2.2 success criterion in wcag-22. For example, a custom combobox with broken keyboard support is cited against family-combobox.md#pattern-combobox-list (for the missing keyboard contract) and ../wcag-22/references/guideline-2-1.md#sc-2-1-1 (for the WCAG 2.1.1 Keyboard requirement).
Focus-management anti-patterns
These are recurring implementation mistakes that pass a static scan (the required ARIA attributes are present) yet break the widget for keyboard and screen-reader users. Each maps to a WCAG success criterion and an APG pattern contract; each is decidable only by driving the keyboard across interaction states, never by attribute presence. This catalog is repository-original content licensed under CC BY 4.0; the pattern contracts it references remain W3C APG material cited above.
Stealing DOM focus to rove a composite popup
Symptom. A combobox, menu, or listbox moves real DOM focus (element.focus()) onto individual options or a footer control as the user arrows through the popup.
Why it breaks. APG composite widgets keep DOM focus on the single container (the input for a combobox, the menu for a menu button) and convey the active option through aria-activedescendant pointing at the highlighted option's id. Moving DOM focus onto a child collapses the combobox focus model: the browser no longer treats the input as focused, so Tab and Shift+Tab act on the page instead of exiting the widget as a unit, and the screen reader loses the combobox context. A handler that jumps focus to a footer link on the first ArrowDown (rather than only after the last option is active) makes every option unreachable.
Correct contract. Let the widget rove options with aria-activedescendant; keep DOM focus on the container throughout. Advance to a trailing control (such as a "see all results" footer option) only when the active descendant is already the last option, give that control role="option" so the listbox children stay valid, and return to the last option on ArrowUp. Tab/Shift+Tab exit the whole widget. Decide with a keyboard-traversal probe or manual pass across the open state — WCAG 2.1.1, 2.4.3; APG pattern-combobox-list, pattern-menu-button.
Not restoring focus after activation or route change
Symptom. Activating a menu item or navigating via an in-page link leaves focus at the document start (or on the skip link) instead of the relevant content, or a closed dialog/menu drops focus to body.
Why it breaks. WCAG 2.4.3 requires focus order to preserve meaning; APG requires focus to return to the triggering control when a menu or dialog closes, and route changes should move focus to the new main content (a <main tabindex="-1">), not the skip link. Leaving focus at the top forces keyboard users to re-traverse the whole page.
Correct contract. On close, return focus to the trigger. On client-side route change, move focus to the main landmark (or a route heading), not the persistent skip link. Decide with a keyboard-traversal probe across focus and open states — WCAG 2.4.3; APG pattern-menu-button, pattern-dialog-modal.
Dual controls for one disclosure
Symptom. A sidebar category or navbar item exposes two adjacent tab stops — a link and a separate expand/collapse caret — that do overlapping jobs, or every leaf in a tree is its own tab stop instead of a single roving-tabindex tree.
Why it breaks. Duplicate controls double the keyboard traversal cost and confuse screen-reader users about which control does what; a flat list of tab stops violates the tree pattern's roving-tabindex contract (categories are tab stops, children are arrow-navigable).
Correct contract. Collapse the category header to a single control, and implement the disclosure as a roving-tabindex tree (or a real button aria-expanded disclosure). Decide with a widget-keyboard and aria-tree probe — WCAG 2.4.3, 1.3.1; APG pattern-tree-view, pattern-disclosure-navigation.
Faux controls (href="#" as a toggle)
Symptom. A dropdown toggle or button is an <a href="#"> with a click handler and no aria-expanded/aria-controls, so activating it jumps the page to the top and exposes no state.
Why it breaks. An anchor is announced as a link, not a button; href="#" scrolls to the top on activation; and with no aria-expanded the screen reader cannot convey open/closed state. A rule engine sees a valid <a> and passes it.
Correct contract. Use a real <button aria-haspopup aria-expanded aria-controls> for the toggle, with Esc/Tab close and focus return. Decide with a widget-keyboard probe plus the faux-semantics heuristic pass — WCAG 4.1.2, 2.4.3; APG pattern-menu-button, pattern-disclosure-navigation.
Skill layout
SKILL.md— this file (skill entrypoint and 44-row roll-up table).references/— one markdown file per APG pattern family. Each file contains a paraphrased description of every pattern in the family, its keyboard interaction contract, its ARIA roles, states, and properties, and a canonical W3C source URL.
ARIA APG family — Combobox
The Combobox family covers single-line input controls that combine a text field with a popup of candidate values. APG distinguishes four combobox variants by their autocomplete behaviour (both, list, none, select-only) and a fifth variant whose popup is a grid rather than a listbox. The standalone listbox pattern lives in this family too because the combobox popup is almost always a listbox and authors reuse the same keyboard contract for both.
Source: W3C WAI-ARIA Authoring Practices Guide, Combobox and Listbox patterns, https://www.w3.org/WAI/ARIA/apg/patterns/combobox/ and https://www.w3.org/WAI/ARIA/apg/patterns/listbox/.
pattern-combobox-autocomplete-both
Combobox (Autocomplete Both) is the combobox variant where typing both filters the visible options in the listbox and automatically inserts the text completion of the closest matching option into the input. The user sees the typed characters as confirmed text and the completion as selected text that can be accepted, edited, or rejected.
Required keyboard
Typing in the input filters the listbox and inserts the closest completion as selected text.
Down arrow opens the listbox if closed and moves focus into the listbox (or to the next option); Up arrow does the same in reverse.
Alt+Down opens the listbox without moving focus into it; Alt+Up closes the listbox.
Enter accepts the highlighted option (or the typed value if no option is highlighted) and closes the listbox.
Escape closes the listbox without changing the input; pressing Escape a second time clears the input.
Backspace and Delete edit the input as expected and rerun the filter.
Required ARIA
role="combobox"on the text input (or on adivwrapping a native<input>) witharia-expanded,aria-controlspointing at the listbox'sid, andaria-autocomplete="both".aria-activedescendanton the combobox points at theidof the currently highlighted option.role="listbox"on the popup container witharia-labeloraria-labelledbynaming the listbox.role="option"on each option, witharia-selected="true"on the currently highlighted option.
Source: https://www.w3.org/WAI/ARIA/apg/patterns/combobox/examples/combobox-autocomplete-both/
pattern-combobox-list
Combobox (List) is the combobox variant where typing filters the listbox but does not automatically insert a completion. The user must explicitly choose an option from the listbox; the input retains exactly what was typed until a selection is committed.
Required keyboard
Typing in the input filters the listbox; the input retains the typed text.
Down arrow opens the listbox and moves focus to the first option (or to the next option when already open); Up arrow does the same in reverse.
Enter commits the highlighted option to the input and closes the listbox.
Escape closes the listbox without changing the input; pressing Escape a second time clears the input.
Backspace edits the input and rerun the filter.
Required ARIA
role="combobox"on the text input witharia-expanded,aria-controls, andaria-autocomplete="list".aria-activedescendanton the combobox points at theidof the currently highlighted option.role="listbox"on the popup container with an accessible name.role="option"on each option, witharia-selected="true"on the highlighted option.
Source: https://www.w3.org/WAI/ARIA/apg/patterns/combobox/examples/combobox-autocomplete-list/
pattern-combobox-none
Combobox (None) is the combobox variant whose listbox does not filter as the user types; the listbox shows the full set of options at all times. The input still accepts free-form text; the listbox simply offers convenient suggestions.
Required keyboard
Typing in the input does not filter the listbox.
Down arrow opens the listbox and moves focus to the first option (or to the next option when already open).
Enter commits the highlighted option (or the typed value) and closes the listbox.
Escape closes the listbox without changing the input.
Required ARIA
role="combobox"on the text input witharia-expanded,aria-controls, andaria-autocomplete="none".aria-activedescendanton the combobox points at theidof the currently highlighted option.role="listbox"on the popup container with an accessible name.role="option"on each option, witharia-selected="true"on the highlighted option.
Source: https://www.w3.org/WAI/ARIA/apg/patterns/combobox/examples/combobox-autocomplete-none/
pattern-combobox-select-only
Combobox (Select-Only) is the combobox variant that behaves like a native <select> element: the user picks one option from a fixed listbox but cannot type free-form text. The "input" is therefore not a text input but a focusable container whose visible label reflects the chosen option.
Required keyboard
Down arrow opens the listbox and moves focus to the next option (or to the first option when closed); Up arrow does the same in reverse.
Enter or Space on the combobox opens or closes the listbox; Enter commits the highlighted option when the listbox is open.
Escape closes the listbox without changing the selection.
Home and End jump focus to the first or last option.
Printable characters jump focus to the next option whose label starts with the typed string.
Required ARIA
The combobox uses
role="combobox"(typically on abuttonor focusablediv) witharia-expanded,aria-controls, and atabindex="0". There is noaria-autocompletevalue because no typing occurs.aria-activedescendanton the combobox points at the highlighted option.role="listbox"on the popup container with an accessible name.role="option"on each option, witharia-selected="true"on the chosen option.
Source: https://www.w3.org/WAI/ARIA/apg/patterns/combobox/examples/combobox-select-only/
pattern-combobox-grid-popup
Combobox (Grid Popup) is the combobox variant whose popup is a grid rather than a listbox. The grid suits cases where each candidate value has multiple columns of information (for example, a date picker showing day, weekday, and additional metadata, or an autocomplete that surfaces multiple attributes per row).
Required keyboard
Typing in the input filters the grid (when filtering is enabled).
Down arrow opens the grid and moves focus to the first row (or first cell) when closed; Up arrow does the same in reverse.
Within the open grid: Left and Right arrows move focus between cells in a row; Down and Up arrows move focus between rows; Home and End jump to the first or last cell in the current row.
Enter commits the currently highlighted row to the input and closes the grid.
Escape closes the grid without changing the input.
Required ARIA
role="combobox"on the text input witharia-expanded,aria-controlspointing at the grid'sid, andaria-haspopup="grid".aria-activedescendanton the combobox points at the currently highlighted cell (or row).role="grid"on the popup container with an accessible name.role="row"on each row,role="gridcell"on cells, androle="columnheader"on the grid's column headers.
Source: https://www.w3.org/WAI/ARIA/apg/patterns/combobox/examples/grid-combo/
pattern-listbox
Listbox is a standalone widget that presents a list of choices for selection. APG supports two selection models: single-select (where one option is selected at a time) and multi-select (where any subset of options may be selected). The listbox is rarely used in isolation in modern UIs but remains the canonical popup for combobox patterns.
Required keyboard
Tab moves focus into and out of the listbox; the listbox itself is one tab stop.
Down and Up arrows move focus between options (changing selection in a single-select listbox); Home and End jump to the first or last option.
Space toggles selection in a multi-select listbox; in a single-select listbox, Space selects the focused option.
Shift+Space (multi-select only) extends selection from the previous anchor to the focused option.
Shift+Down or Shift+Up (multi-select only) extends selection by one option.
Ctrl+A (multi-select, optional) selects all options.
Printable characters jump focus to the next option whose label starts with the typed string.
Required ARIA
The container uses
role="listbox"witharia-orientationset when the orientation is not the default vertical.aria-multiselectable="true"when the listbox supports multi-selection.role="option"on each option, witharia-selected="true"on selected options andaria-selected="false"on unselected options in a multi-select listbox.aria-disabled="true"on options that cannot currently be selected.
Source: https://www.w3.org/WAI/ARIA/apg/patterns/listbox/
ARIA APG family — Dialog
The Dialog family is the catch-all bucket per the ARIA APG source rollup. The "true" dialog widgets (alert, alertdialog, dialog) form the core, but the rollup also groups here a wide range of patterns that did not fall into another family: live regions (alert, status, log via the notification live-region pattern), interactive form controls (switch, radio variants, checkbox variants, button), display widgets (meter, tooltip), structural primitives (window splitter, breadcrumb, feed), and landmark guidance. Sections in this file are independent; the family grouping reflects APG's rollup ordering, not a shared behavioural contract.
Source: W3C WAI-ARIA Authoring Practices Guide, Dialog and supporting patterns, https://www.w3.org/WAI/ARIA/apg/patterns/dialog-modal/.
pattern-alert
Alert is the live-region pattern for an unsolicited, important message that requires the user's attention but does not interrupt the user's task. The alert appears in the page, is announced by assistive technology immediately, and does not move focus.
Required keyboard
No keyboard interaction inside the alert itself; the alert does not take focus.
Focus may move to a dismiss control inside the alert (such as a close button), but the alert region itself is non-interactive.
Required ARIA
The alert container uses
role="alert"(which implicitly setsaria-live="assertive"andaria-atomic="true").The alert text appears inside the container at the moment the alert should be announced; assistive technology announces the text on insertion.
aria-atomic="false"may be set when the alert region updates progressively and only the changed portion should be announced.
Source: https://www.w3.org/WAI/ARIA/apg/patterns/alert/
pattern-alert-dialog-modal
Alert Dialog (Modal) is a modal dialog that carries an urgent message and at least one actionable control (typically OK and Cancel). The pattern combines the alert pattern's announcement semantics with the modal dialog pattern's focus-trap and Escape-to-dismiss contract.
Required keyboard
Tab and Shift+Tab move focus among the dialog's focusable controls, wrapping at the ends of the focus order to stay inside the dialog.
Escape dismisses the dialog (and triggers the cancellation action).
Enter on the default button activates that button.
Focus moves into the dialog when it opens (typically to the most relevant button or to a heading inside the dialog) and returns to the triggering element when the dialog closes.
Required ARIA
The dialog container uses
role="alertdialog"witharia-modal="true".aria-labelledbypoints at the dialog title;aria-describedbypoints at the dialog's descriptive text.Background content outside the dialog is removed from the accessibility tree with
inert(oraria-hidden="true"wheninertis not available).
Source: https://www.w3.org/WAI/ARIA/apg/patterns/alertdialog/
pattern-dialog-modal
Dialog (Modal) is the general modal dialog pattern: a window that interrupts the user's workflow, traps keyboard focus, and requires explicit dismissal before the user can interact with the rest of the page. Modal dialogs are the standard pattern for forms, confirmations, and detail editors that must complete or cancel before continuing.
Required keyboard
Tab and Shift+Tab cycle focus among the dialog's focusable controls; focus wraps from the last control to the first and vice versa.
Escape dismisses the dialog.
Enter on the default button activates that button.
Focus moves into the dialog when it opens (typically to the first focusable control or to a designated initial element) and returns to the triggering element when the dialog closes.
Required ARIA
The dialog container uses
role="dialog"witharia-modal="true".aria-labelledbypoints at the dialog title;aria-describedby(optional) points at supplementary descriptive content.Background content outside the dialog is made inert with the
inertattribute (or hidden witharia-hidden="true"as a fallback).
Source: https://www.w3.org/WAI/ARIA/apg/patterns/dialog-modal/
pattern-feed
Feed is a scrollable list of articles, where new articles are appended (or prepended) as the user scrolls. The pattern coordinates keyboard navigation between articles and lazy-loading of additional content, while announcing the new content to assistive technology as it arrives.
Required keyboard
Tab moves focus into and out of the feed.
Page Down moves focus to the next article in the feed; Page Up moves focus to the previous article.
Control+End moves focus to the last article currently loaded; Control+Home moves focus to the first article.
Tab within an article moves focus among the article's interactive controls.
Required ARIA
The container uses
role="feed"witharia-busy="true"while loading additional articles.Each article uses
role="article"witharia-posinsetandaria-setsizeindicating its position in the feed;aria-labelledbypoints at the article's title.When more articles are appended, the new articles'
aria-posinsetandaria-setsizereflect the updated count.
Source: https://www.w3.org/WAI/ARIA/apg/patterns/feed/
pattern-breadcrumb
Breadcrumb is a navigation pattern that shows the user's location within the site's hierarchy as a sequence of links from the root to the current page. The current page is included as a non-link item to provide context for the user's current position.
Required keyboard
Tab moves focus to each link in the breadcrumb trail in document order.
Enter activates the focused link (standard
<a>behaviour).
Required ARIA
The container uses
nav(orrole="navigation") witharia-label="Breadcrumb".The breadcrumb items are arranged in an ordered list (
<ol>).Each item except the last is a native
<a>link; the last item represents the current page and usesaria-current="page".
Source: https://www.w3.org/WAI/ARIA/apg/patterns/breadcrumb/
pattern-notification-live-region
Notification (Live Region) covers the family of polite, non-interruptive live regions: status, log, timer, and the generic aria-live="polite" region. These regions announce updates to assistive technology without moving focus and without interrupting current speech, in contrast to role="alert" which interrupts.
Required keyboard
No keyboard interaction with the live region itself; the region does not take focus.
Required ARIA
The region uses one of
role="status"(general advisory information),role="log"(a sequence of additions such as a chat transcript),role="timer"(an elapsed or remaining time indicator), or a bare container witharia-live="polite"andaria-atomicset appropriately.aria-live="polite"is implicit forrole="status"androle="log"; the value can be overridden when the announcement urgency differs.aria-atomic="true"requests that the entire region be reannounced on every change;aria-atomic="false"(the default for log) announces only the additions.aria-relevant(optional) tunes which mutations trigger announcement (additions,removals,text,all).
Source: https://www.w3.org/WAI/ARIA/apg/practices/
pattern-switch
Switch is a two-state input that turns a setting on or off. The switch differs from a checkbox in its presentation (typically a sliding visual rather than a tick box) and in the semantic intent: a switch represents an immediately applied setting, whereas a checkbox represents a deferred selection.
Required keyboard
Tab moves focus to and away from the switch.
Space toggles the switch's on/off state; Enter (optional) does the same.
Required ARIA
The control uses
role="switch"(or the native<input type="checkbox" role="switch">pattern in some implementations).aria-checked="true"indicates the on state;aria-checked="false"indicates the off state.aria-readonly="true"indicates that the switch is informational and cannot be toggled by the user.aria-disabled="true"indicates that the switch is currently unavailable.
Source: https://www.w3.org/WAI/ARIA/apg/patterns/switch/
pattern-radio-group
Radio Group is a set of mutually exclusive choices in which exactly one radio button is selected at a time. APG's reference implementation uses native focus (each radio is a separate tab stop); see pattern-radio-group-roving-tabindex for the variant that uses managed focus.
Required keyboard
Tab moves focus to the selected radio button (or to the first radio button when none is selected), then out of the group.
Down and Right arrows move focus to and select the next radio button in the group (wrapping to the first); Up and Left arrows do the same in reverse (wrapping to the last).
Space selects the focused radio button if it is not already selected.
Required ARIA
The group container uses
role="radiogroup"witharia-labelledbyoraria-labelnaming the group.Each radio uses
role="radio"witharia-checked="true"on the selected radio andaria-checked="false"on the rest.aria-required="true"indicates that a selection must be made before form submission.aria-disabled="true"on individual radios indicates that they cannot currently be selected.
Source: https://www.w3.org/WAI/ARIA/apg/patterns/radio/
pattern-radio-group-roving-tabindex
Radio Group (Roving Tabindex) is the radio group variant that uses managed focus: the entire group is one tab stop, and arrow keys move focus between radios within the group. This variant suits radio groups embedded in a composite widget (such as a toolbar) where a per-radio tab stop would disrupt the overall tab order.
Required keyboard
Tab moves focus to the currently selected radio (or to the first radio when none is selected), then out of the group on the next Tab press.
Down and Right arrows move focus to and select the next radio in the group (wrapping to the first); Up and Left arrows do the same in reverse.
Space selects the focused radio when it is not already selected.
Required ARIA
The container uses
role="radiogroup"with an accessible name.Each radio uses
role="radio"witharia-checked.Exactly one radio carries
tabindex="0"(typically the selected one); the rest usetabindex="-1".
Source: https://www.w3.org/WAI/ARIA/apg/patterns/radio/examples/radio-activedescendant/
pattern-checkbox-dual-state
Checkbox (Dual-State) is the standard checkbox: a two-state input representing either checked or unchecked. The checkbox is the standard control for individual on/off selections that are committed as part of a form submission rather than applied immediately.
Required keyboard
Tab moves focus to and away from the checkbox.
Space toggles the checkbox between checked and unchecked.
Required ARIA
The control uses
role="checkbox"(or the native<input type="checkbox">element, which exposes the role implicitly).aria-checked="true"for checked;aria-checked="false"for unchecked.aria-required="true"indicates that the checkbox must be checked before form submission.aria-disabled="true"indicates that the checkbox is currently unavailable.aria-describedby(optional) points at supplementary descriptive text.
Source: https://www.w3.org/WAI/ARIA/apg/patterns/checkbox/examples/checkbox/
pattern-checkbox-mixed-state
Checkbox (Mixed-State) is the tri-state checkbox: in addition to checked and unchecked, the control can be in a mixed (indeterminate) state. The mixed state suits parent checkboxes that summarise a set of child checkboxes; the parent is mixed when some but not all children are checked.
Required keyboard
Tab moves focus to and away from the checkbox.
Space cycles the checkbox among checked, mixed, and unchecked (or among checked and unchecked when the mixed state is set programmatically rather than via direct user activation).
Required ARIA
The control uses
role="checkbox".aria-checked="mixed"represents the indeterminate state;aria-checked="true"andaria-checked="false"represent the other two states.When the checkbox summarises a set of children, JavaScript updates the parent's
aria-checkedvalue as the children's states change.
Source: https://www.w3.org/WAI/ARIA/apg/patterns/checkbox/examples/checkbox-mixed/
pattern-button
Button is the activation primitive: a control that fires an action when activated. APG advises authors to use the native <button> element whenever possible because it brings the keyboard contract, the disabled-state behaviour, and the focusable behaviour for free.
Required keyboard
Tab moves focus to and away from the button.
Enter activates the button.
Space activates the button on release (the native behaviour of
<button>).
Required ARIA
Prefer the native
<button>element. Userole="button"withtabindex="0"only when the host element is not a button.aria-pressed="true"oraria-pressed="false"exposes a toggle button's state;aria-pressed="mixed"represents a partially pressed state.aria-expandeddescribes whether a button reveals an associated region (disclosure button, combobox button, menu button).aria-haspopupindicates that activation reveals a popup; the value names the popup's kind (menu,listbox,tree,grid,dialog).aria-disabled="true"indicates a button whose action is currently unavailable; unlike the nativedisabledattribute, anaria-disabledbutton remains focusable so users can discover the disabled state.
Source: https://www.w3.org/WAI/ARIA/apg/patterns/button/
pattern-meter
Meter is a display widget that represents a known value within a known range, typically with semantic colour zones (low, optimal, high). The meter differs from a progressbar because it represents a static measurement rather than progress toward completion; battery level and disk usage are typical meter use cases.
Required keyboard
No keyboard interaction; the meter is non-interactive and does not take focus.
Required ARIA
The container uses
role="meter"(or the native<meter>element, which exposes the role implicitly).aria-valuenowreflects the current value.aria-valueminandaria-valuemaxbound the value range.aria-valuetextprovides a human-readable representation when the numeric value alone is insufficient ("Battery at 47 percent" rather than "47").aria-labeloraria-labelledbysupplies the accessible name.
Source: https://www.w3.org/WAI/ARIA/apg/patterns/meter/
pattern-tooltip
Tooltip is a small contextual popup that appears next to an element to provide a short descriptive label or hint. APG specifies a strict contract: the tooltip appears on focus and on hover, does not take focus, dismisses on Escape, and is associated with its trigger via aria-describedby so that the tooltip text augments rather than replaces the trigger's accessible name.
Required keyboard
Focus on the trigger element shows the tooltip; focus leaving the trigger hides the tooltip.
Escape (while the trigger has focus) hides the tooltip without moving focus.
No keyboard interaction with the tooltip itself; the tooltip does not take focus.
Required ARIA
The tooltip container uses
role="tooltip"with anid.The trigger element references the tooltip with
aria-describedbypointing at the tooltip'sid.The tooltip text is short and supplementary; it does not duplicate the trigger's visible label.
Source: https://www.w3.org/WAI/ARIA/apg/patterns/tooltip/
pattern-window-splitter
Window Splitter is the resizable divider between two adjacent panes (for example, a vertical splitter between a navigation pane and a content pane). The splitter is focusable and adjustable by keyboard so that users who cannot drag with a pointer can still resize the panes.
Required keyboard
Tab moves focus to and away from the splitter.
Right or Up arrow increases the size of the leading pane by a small step; Left or Down arrow decreases it by the same step (for a vertical splitter, the arrow assignment reflects whether the splitter is horizontal or vertical).
Page Up and Page Down change the size by a larger step.
Home collapses the leading pane to its minimum size; End expands it to its maximum size.
Enter (optional) toggles between the most recent size and the collapsed state.
Required ARIA
The splitter uses
role="separator"withtabindex="0"andaria-orientationdescribing the splitter's axis.aria-valuenowreflects the current size of the leading pane (as a percentage or absolute value).aria-valueminandaria-valuemaxbound the size range.aria-controlspoints at the pane (or panes) being resized.aria-labeloraria-labelledbysupplies an accessible name (such as "Resize navigation pane").
Source: https://www.w3.org/WAI/ARIA/apg/patterns/windowsplitter/
pattern-landmarks
Landmarks is the structural-navigation pattern: the page is divided into well-named regions that screen readers expose as a landmark map, so that users can jump directly to (for example) the main content, the primary navigation, or the search region. APG advises authors to use the native HTML sectioning elements (main, nav, aside, header, footer, section, form) wherever possible because they expose the corresponding landmark roles implicitly.
Required keyboard
No keyboard interaction with the landmarks themselves; landmarks are navigated via the screen reader's landmark-navigation gestures (typically D in NVDA or VoiceOver, or the screen reader's landmark list).
Required ARIA
Prefer the native sectioning elements:
<main>(role="main"),<nav>(role="navigation"),<aside>(role="complementary"),<header>and<footer>(which exposerole="banner"androle="contentinfo"when they are direct children of<body>),<section>with an accessible name (role="region"), and<form>with an accessible name (role="form").Apply
aria-labeloraria-labelledbyto differentiate landmarks of the same role (for example, two<nav>elements labelled "Main" and "Footer").Use the search landmark (
role="search") on the container around a site-wide search control.Exactly one
<main>(orrole="main") appears on the page.
Source: https://www.w3.org/WAI/ARIA/apg/practices/landmark-regions/
ARIA APG family — Disclosure
The Disclosure family covers widgets whose primary behaviour is showing or hiding a region of related content. APG groups the simple show/hide disclosure together with composite patterns built on the same expand-and-collapse mechanic: accordions (a vertical stack of disclosures), carousels (a rotating disclosure of one panel at a time), and disclosure navigation menus (a navigation menu whose submenus appear via the disclosure pattern rather than the menu pattern). The hybrid disclosure-navigation variant combines a link as the top-level item with a separate disclosure button for the submenu.
Source: W3C WAI-ARIA Authoring Practices Guide, Disclosure and related patterns, https://www.w3.org/WAI/ARIA/apg/patterns/disclosure/.
pattern-accordion
Accordion is a vertical stack of disclosure controls. Each header is a button that expands or collapses an associated region; accordions may permit only one panel open at a time (single-expand) or any combination of panels open at the same time (multi-expand).
Required keyboard
Tab moves focus to the next header in the accordion, then out of the accordion.
Enter or Space on a header toggles the expanded state of its panel.
Down arrow (optional) moves focus to the next accordion header (wrapping to the first); Up arrow (optional) moves focus to the previous header (wrapping to the last).
Home (optional) moves focus to the first header; End (optional) moves focus to the last header.
Required ARIA
Each header uses a
button(orrole="button") witharia-expanded="true"oraria-expanded="false"andaria-controlspointing at theidof its panel.Each panel uses
role="region"(or a semantically equivalent landmark) witharia-labelledbypointing at its header'sid.The hidden panel is fully removed from the accessibility tree (via
hiddenordisplay: none) when collapsed.
Source: https://www.w3.org/WAI/ARIA/apg/patterns/accordion/
pattern-disclosure-show-hide
Disclosure (Show/Hide) is the minimal pattern: a single button that toggles the visibility of one region of content. The pattern is the building block for more complex disclosures (accordions, navigation menus, expandable filters).
Required keyboard
Tab moves focus to and away from the disclosure button.
Enter or Space toggles the expanded state of the controlled region.
Required ARIA
The trigger uses a
button(orrole="button") witharia-expanded="true"oraria-expanded="false".aria-controlson the button (optional but recommended) points at theidof the controlled region.The region is fully removed from the accessibility tree when collapsed.
Source: https://www.w3.org/WAI/ARIA/apg/patterns/disclosure/
pattern-carousel-auto-rotating
Carousel (Auto-Rotating) is a carousel that advances through its slides automatically on a timer. APG requires an obvious way for users to pause and resume rotation so that the auto-advance does not interfere with reading or interacting with slide content. The pattern emphasises pause-on-hover and pause-on-focus behaviour in addition to the explicit pause button.
Required keyboard
Tab moves focus to the carousel controls (pause button, previous, next, and slide picker).
Space or Enter on the pause button toggles auto-rotation; while paused, the carousel does not advance.
Left and Right arrows on the previous and next buttons advance to the previous or next slide.
Focus entering any descendant of the carousel pauses auto-rotation; focus leaving the carousel resumes it (when previously rotating).
Required ARIA
The container uses
role="region"(orrole="group") witharia-roledescription="carousel"and anaria-labeloraria-labelledbynaming the carousel.Each slide container uses
aria-roledescription="slide"and anaria-labeloraria-labelledbyidentifying the slide.The live region wrapping the slide track uses
aria-live="off"while auto-rotating andaria-live="polite"while paused, so that announcement does not collide with the auto-advance.The pause button uses
aria-labeldescribing the current action ("Stop slide rotation" or "Start slide rotation").aria-controlson the previous, next, and slide-picker controls points at the slide track.
Source: https://www.w3.org/WAI/ARIA/apg/patterns/carousel/examples/carousel-1-prev-next/
pattern-carousel-tabbed
Carousel (Tabbed) is a carousel whose slide picker takes the form of a tablist: each tab represents one slide, and activating a tab displays its slide as the carousel's current panel. The pattern reuses the Tabs keyboard contract for slide navigation; the previous and next buttons are optional.
Required keyboard
Tab moves focus into the tablist (and from there into the rotation controls, when present).
Left and Right arrows move focus between tabs in the tablist; activation may be manual or automatic per
pattern-tabs-manual-activationandpattern-tabs-automatic-activation.Enter or Space activates the focused tab and displays the matching slide (when activation is manual).
Home and End move focus to the first or last tab.
Required ARIA
The container uses
role="region"(orrole="group") witharia-roledescription="carousel"and an accessible name.The slide picker uses
role="tablist", with each picker control usingrole="tab"andaria-controlspointing at its slide.Each slide uses
role="tabpanel"witharia-labelledbypointing at its owning tab.The active tab uses
aria-selected="true"; other tabs usearia-selected="false".
Source: https://www.w3.org/WAI/ARIA/apg/patterns/carousel/examples/carousel-2-tablist/
pattern-disclosure-navigation
Disclosure Navigation is a navigation menu whose submenus open via the disclosure pattern rather than the menu pattern. Top-level items are buttons that expand vertical lists of links; the keyboard contract differs from pattern-menubar-navigation because submenus do not use roving tabindex and arrow keys do not move focus between top-level items.
Required keyboard
Tab moves focus through each top-level disclosure button and (when open) through the links in the open submenu.
Enter or Space on a top-level button toggles the expansion of its submenu.
Escape (optional) closes the open submenu and returns focus to its trigger button.
Tab away from the last visible link closes any open submenu.
Required ARIA
The outer container uses a
navelement (orrole="navigation") with an accessible name.Each top-level item uses a
button(orrole="button") witharia-expandedandaria-controlspointing at its submenu container.Submenu items are native
<a>links inside a<ul>or equivalent list container.Closed submenus are hidden via
hiddenordisplay: noneso that they are removed from the accessibility tree.
Source: https://www.w3.org/WAI/ARIA/apg/patterns/disclosure/examples/disclosure-navigation/
pattern-disclosure-navigation-hybrid
Disclosure Navigation Hybrid is the disclosure-navigation pattern in which the top-level item is itself a link to a destination, and a separate disclosure button sits next to it to expand the submenu. The hybrid pattern suits sites where the top-level navigation item must be directly clickable as a landing page, but the user must also be able to expand the submenu without leaving the current page.
Required keyboard
Tab moves focus to the top-level link, then to its adjacent disclosure button, then to the submenu links when the submenu is open, then on to the next top-level link.
Enter or Space on the top-level link follows the link (standard
<a>behaviour).Enter or Space on the disclosure button toggles the submenu.
Arrow keys (optional) on the disclosure button or in the submenu may move focus between submenu items.
Escape (optional) closes the open submenu and returns focus to the disclosure button.
Required ARIA
The outer container uses
nav(orrole="navigation") with an accessible name.The top-level link is a native
<a>element.The adjacent disclosure button uses
aria-expandedandaria-controlspointing at its submenu container, plus anaria-label(such as "Show submenu for Products") because the visible label is typically an icon.aria-haspopup="true"(optional) on the disclosure button signals that activation reveals a popup region.Submenu items are native
<a>links inside a list container.
Source: https://www.w3.org/WAI/ARIA/apg/patterns/disclosure/examples/disclosure-navigation-hybrid/
ARIA APG family — Grid
The Grid family covers two-dimensional navigation widgets and their numeric input siblings. APG distinguishes three grid variants by their content and selection model (layout, data with single-cell selection, data with multi-cell selection) and includes the spinbutton and slider patterns here because they share the grid family's cell-navigation primitives (focused cell with arrow-key movement and Page Up or Page Down for large steps).
Source: W3C WAI-ARIA Authoring Practices Guide, Grid, Spinbutton, and Slider patterns, https://www.w3.org/WAI/ARIA/apg/patterns/grid/.
pattern-grid-layout
Grid (Layout) is the variant where the grid is used to lay out a collection of widgets in a two-dimensional structure (for example, a toolbar matrix or a collection of buttons). The cells contain interactive widgets rather than tabular data; selection is typically a single focused cell rather than a selected range.
Required keyboard
Tab moves focus into and out of the grid; the grid itself is one tab stop.
Within the grid: Left and Right arrows move focus between cells in a row; Down and Up arrows move focus between rows.
Home moves focus to the first cell in the current row; End moves focus to the last cell in the current row.
Ctrl+Home moves focus to the first cell in the grid; Ctrl+End moves focus to the last cell.
Enter or Space (optional) activates the widget inside the focused cell.
Required ARIA
The container uses
role="grid".Rows use
role="row"; cells userole="gridcell".aria-selectedreflects per-cell selection state when selection is supported.Only the focused cell carries
tabindex="0"; other cells usetabindex="-1".
Source: https://www.w3.org/WAI/ARIA/apg/patterns/grid/examples/layout-grids/
pattern-grid-data-single-cell
Grid (Data, Single-Cell Selection) is the variant where the grid presents tabular data and the user can select exactly one cell at a time. Headers, sortable columns, and read-only behaviour are common features of the data-grid variants.
Required keyboard
Tab moves focus into and out of the grid.
Left and Right arrows move focus between cells in a row; Down and Up arrows move focus between rows.
Home and End move focus to the first or last cell in the current row.
Ctrl+Home and Ctrl+End move focus to the first or last cell in the grid.
Enter or Space selects the focused cell (or invokes the cell's edit mode in editable grids).
F2 (optional) enters edit mode on an editable cell.
Required ARIA
The container uses
role="grid"witharia-rowcountandaria-colcountwhen the data set is virtualised.Rows use
role="row"witharia-rowindexwhen virtualised.Cells use
role="gridcell"witharia-colindexwhen virtualised; header cells userole="columnheader"orrole="rowheader".aria-selected="true"on the currently selected cell.aria-readonly="true"on the grid (or on individual cells) when content cannot be edited.
Source: https://www.w3.org/WAI/ARIA/apg/patterns/grid/examples/data-grids/
pattern-grid-data-multi-cell
Grid (Data, Multi-Cell Selection) is the variant where the user can select a range of cells, an entire row, or an entire column. The keyboard contract extends pattern-grid-data-single-cell with modifier-augmented arrow keys for range selection and an explicit "select all" gesture.
Required keyboard
Arrow keys, Home, End, Ctrl+Home, and Ctrl+End behave as in
pattern-grid-data-single-cellto move focus.Shift+arrow keys extend the current selection by one cell in the arrow's direction.
Shift+Home and Shift+End extend selection to the start or end of the current row.
Ctrl+Space selects (or deselects) the entire current column; Shift+Space selects (or deselects) the entire current row.
Ctrl+A selects all cells in the grid.
Enter or Space on the focused cell toggles selection of that cell.
Required ARIA
The container uses
role="grid"witharia-multiselectable="true".Rows use
role="row"; cells userole="gridcell", witharia-selected="true"oraria-selected="false"on every cell in the selection-capable region.Header cells use
role="columnheader"orrole="rowheader"as appropriate.aria-rowcount,aria-colcount,aria-rowindex, andaria-colindexdescribe virtualised grids.
Source: https://www.w3.org/WAI/ARIA/apg/patterns/grid/examples/data-grids/
pattern-spinbutton
Spinbutton is a numeric input augmented with increment and decrement controls. Users may type a value directly into the input or step through the value range with arrow-key, page-key, and Home or End shortcuts. The spinbutton is the recommended role for numeric inputs whose value range is bounded and known.
Required keyboard
Up arrow increments the value by one step; Down arrow decrements by one step.
Page Up increments by a larger step (typically ten times the base step); Page Down decrements by the same larger step.
Home sets the value to
aria-valuemin; End sets the value toaria-valuemax.Typing inside the input edits the value directly (per native text-input behaviour).
Required ARIA
The input uses
role="spinbutton"(or the native<input type="number">element, which exposes the role implicitly).aria-valuenowreflects the current value.aria-valueminandaria-valuemaxbound the value range.aria-valuetextprovides a human-readable representation when the numeric value alone is insufficient (for example, "10 minutes" rather than "10").aria-disabled="true"indicates that the spinbutton cannot accept input.aria-required="true"indicates that the spinbutton must hold a value before form submission.
Source: https://www.w3.org/WAI/ARIA/apg/patterns/spinbutton/
pattern-slider-single-thumb
Slider (Single-Thumb) is a value-picker widget with a single thumb that slides along a track between minimum and maximum bounds. The pattern suits any continuous-range numeric input where a visual track conveys the value better than a typed number (volume, brightness, threshold).
Required keyboard
Right or Up arrow increments the value by one step; Left or Down arrow decrements by one step.
Page Up increments by a larger step (typically one tenth of the range); Page Down decrements by the same larger step.
Home sets the value to
aria-valuemin; End sets the value toaria-valuemax.
Required ARIA
The thumb uses
role="slider"withtabindex="0".aria-valuenowreflects the current value.aria-valueminandaria-valuemaxbound the value range.aria-valuetextprovides a human-readable representation when needed.aria-orientation="horizontal"(the default) oraria-orientation="vertical"describes the slider axis.aria-labeloraria-labelledbysupplies the accessible name.aria-disabled="true"indicates that the slider cannot be adjusted.
Source: https://www.w3.org/WAI/ARIA/apg/patterns/slider/
pattern-slider-two-thumb
Slider (Two-Thumb) is a range-picker widget with two thumbs on the same track. Each thumb represents one end of a value range (for example, a price filter with minimum and maximum). The two thumbs operate independently but cannot cross each other.
Required keyboard
Tab moves focus to the lower thumb, then to the upper thumb, then out of the slider.
On the focused thumb: Right or Up arrow increments by one step; Left or Down arrow decrements by one step.
Page Up and Page Down change the value by a larger step.
Home sets the focused thumb to its minimum bound (the slider's
aria-valueminfor the lower thumb, or the current lower-thumb value for the upper thumb); End sets it to its maximum bound (the current upper-thumb value for the lower thumb, or the slider'saria-valuemaxfor the upper thumb).
Required ARIA
Each thumb uses
role="slider"with its ownaria-valuenow,aria-valuemin,aria-valuemax, andaria-valuetext.Each thumb has an
aria-labeldistinguishing it (for example, "Minimum price" and "Maximum price").aria-orientationdescribes the slider axis.The lower thumb's
aria-valuemaxupdates to the upper thumb's current value (and vice versa) so that the bounds prevent the thumbs from crossing.
Source: https://www.w3.org/WAI/ARIA/apg/patterns/slider-multithumb/
ARIA APG family — Menu
The Menu family covers application menus, menu buttons, menubars, and menubars that support multi-selection of menu items. Menu widgets behave like desktop application menus rather than navigation menus: users move focus with arrow keys, open submenus along the perpendicular axis, and dismiss menus with Escape. Navigation menus that look like menubars but use links instead of menuitems are documented as pattern-menubar-navigation to make the distinction explicit.
Source: W3C WAI-ARIA Authoring Practices Guide, Menu and Menubar patterns, https://www.w3.org/WAI/ARIA/apg/patterns/menu-button/ and https://www.w3.org/WAI/ARIA/apg/patterns/menubar/.
pattern-menu-button
Menu Button is a button that opens a popup menu of actions, options, or commands. The button acts as the menu's trigger and its accessible reference; the menu itself follows the standard role="menu" keyboard contract once it opens.
Required keyboard
When focus is on the button: Enter, Space, or Down arrow opens the menu and moves focus to the first menu item; Up arrow opens the menu and moves focus to the last menu item.
When focus is in the menu: Down and Up arrows move focus between menu items (wrapping at the ends); Home and End jump to the first and last items.
Escape closes the menu and returns focus to the button.
Tab closes the menu and moves focus to the next focusable element after the button.
Enter or Space on a focused menu item activates the item and closes the menu.
Printable characters jump focus to the next menu item whose visible label starts with the typed character.
Required ARIA
The button uses
aria-haspopup="menu"andaria-expanded="true"oraria-expanded="false"; when the menu is open,aria-controlson the button points at the menu'sid.The popup uses
role="menu", with each item usingrole="menuitem"(orrole="menuitemcheckbox"orrole="menuitemradio"when stateful).Submenu triggers use
aria-haspopup="menu"andaria-expanded.The first menu item receives focus when the menu opens; subsequent navigation uses managed focus.
Source: https://www.w3.org/WAI/ARIA/apg/patterns/menu-button/
pattern-menubar-editor
Menubar (Editor) is the editor-style menubar found in desktop applications such as word processors. Top-level menu items live in a horizontal menubar, and each top-level item opens a vertical dropdown menu that may contain submenus. The pattern emphasises rapid keyboard navigation between menus.
Required keyboard
Tab moves focus into and out of the menubar; the menubar itself is one tab stop.
Within the menubar: Left and Right arrows move focus between top-level items (wrapping at the ends); Down arrow or Enter opens the focused menu and moves focus to its first item; Up arrow opens the menu and moves focus to its last item.
Within an open menu: Down and Up arrows move focus between items; Right arrow opens a submenu or moves to the next top-level menu; Left arrow closes the current submenu (or moves to the previous top-level menu when at the root).
Enter or Space activates the focused menuitem and closes the menu chain.
Escape closes the current menu and returns focus to its trigger (or to the menubar item for top-level menus).
Printable characters jump focus to the next item whose label starts with the typed character.
Required ARIA
The container uses
role="menubar"witharia-orientation="horizontal".Top-level items use
role="menuitem"witharia-haspopup="menu"andaria-expanded.Submenus use
role="menu"witharia-orientation="vertical".Submenu items use
role="menuitem",role="menuitemcheckbox", orrole="menuitemradio"as appropriate.Only one element in the menubar carries
tabindex="0"at any time; the rest usetabindex="-1".
Source: https://www.w3.org/WAI/ARIA/apg/patterns/menubar/examples/menubar-editor/
pattern-menubar-navigation
Menubar (Navigation) is the navigation-style variant of a menubar, used as a site-wide or section-level navigation bar. The visible behaviour resembles pattern-menubar-editor, but the leaf items are links that navigate to URLs rather than menuitems that fire JavaScript actions. APG keeps the pattern separate because the activation contract differs: leaf items are activated by following a link rather than by invoking an application command.
Required keyboard
Tab moves focus into and out of the navigation menubar.
Left and Right arrows move focus between top-level items; Down arrow opens the submenu under the focused top-level item.
Within an open submenu: Down and Up arrows move focus between submenu items (which are typically links); Right arrow opens a nested submenu; Left arrow closes the current submenu and returns focus to its trigger.
Enter or Space on a leaf link follows the link (the same as activating an
<a>element directly).Escape closes the current submenu.
Required ARIA
The container uses
role="menubar"witharia-orientation="horizontal"and an accessible name (aria-labelsuch as "Main").Top-level items that open a submenu use
role="menuitem"witharia-haspopup="menu"andaria-expanded.Leaf items use native
<a>elements; the surroundingrole="menu"androle="none"wrappers expose the structure to assistive technology without breaking link semantics.Submenus use
role="menu"witharia-orientation="vertical".
Source: https://www.w3.org/WAI/ARIA/apg/patterns/menubar/examples/menubar-navigation/
pattern-actions-menu-button
Actions Menu Button is a specialised menu button whose menu items represent contextual actions on a target object (for example, the row-level actions menu in a data grid or the per-item kebab menu in a list view). The keyboard contract is identical to pattern-menu-button; APG documents the pattern separately to highlight authoring guidance about labelling, contextual scope, and menu placement.
Required keyboard
Enter, Space, or Down arrow on the button opens the menu and moves focus to the first action.
Up arrow on the button opens the menu and moves focus to the last action.
Within the menu: Down and Up arrows move focus between actions; Home and End jump to the first or last action.
Escape closes the menu and returns focus to the button.
Tab closes the menu and moves focus to the next focusable element.
Enter or Space activates the focused action and closes the menu.
Printable characters jump focus to the next action whose label starts with the typed character.
Required ARIA
The button uses
aria-haspopup="menu",aria-expanded, andaria-controls. The button label identifies both the context and the kind of menu (for example, "Actions for Smith order #4421").The popup uses
role="menu"witharia-labeldescribing the target of the actions.Each action uses
role="menuitem", orrole="menuitemcheckbox"orrole="menuitemradio"when the action toggles state.
Source: https://www.w3.org/WAI/ARIA/apg/patterns/menu-button/examples/menu-button-actions-active-descendant/
pattern-menubar-multi-select
Menubar with Multiple Selection is a menubar variant where items support multi-selection through role="menuitemcheckbox" or role="menuitemradio". Users toggle selection state on each item without dismissing the menu, which suits filter menus, column-picker menus, and tag-assignment menus.
Required keyboard
Tab moves focus into and out of the menubar.
Left and Right arrows move focus between top-level menus; Down arrow opens a menu and moves focus to its first item.
Within an open menu: Down and Up arrows move focus between items; Home and End jump to the first or last item.
Space toggles the selection state of the focused
menuitemcheckboxormenuitemradiowithout closing the menu.Enter activates the focused item; on a checkbox or radio item the behaviour is the same as Space but typically also closes the menu in the default APG implementation.
Shift+arrow keys (optional) extend selection across a contiguous range of items.
Ctrl+A (optional) selects all items in the current menu when multi-selection is permitted.
Escape closes the menu and returns focus to its menubar trigger.
Required ARIA
The container uses
role="menubar"witharia-orientation="horizontal".Submenus use
role="menu"witharia-orientation="vertical".Multi-select items use
role="menuitemcheckbox"witharia-checked="true"oraria-checked="false".Single-select-within-group items use
role="menuitemradio"witharia-checkedand arole="group"wrapper that scopes the radio set.aria-multiselectable="true"may appear on the parent menu when grouped multi-selection is supported across items.
Source: https://www.w3.org/WAI/ARIA/apg/patterns/menubar/examples/menubar-editor/
ARIA APG family — Tabs
The Tabs family covers tabbed-content interfaces, where a row of tabs controls which of several tab panels is visible. APG separates the family into two activation modes (manual and automatic) because the keyboard contract differs even though the markup is identical. The third historical APG entry, Tabbed Carousel, lives in family-disclosure.md alongside the other carousel variants because the source rollup groups all three carousel patterns there.
Source: W3C WAI-ARIA Authoring Practices Guide, Tabs family, https://www.w3.org/WAI/ARIA/apg/patterns/tabs/.
pattern-tabs-manual-activation
Tabs (Manual Activation) is the variant where moving keyboard focus along the tablist does not change which panel is visible. The user moves focus with the arrow keys and then presses Enter or Space to activate the focused tab and reveal its panel. This is the recommended activation mode when revealing a panel is expensive (large data fetch, layout reflow, or destructive side effects).
Required keyboard
Tab moves focus to the active tab when entering the tablist, and out of the tablist on the next Tab press.
Left and Right arrows (for horizontal tablists) or Up and Down arrows (for vertical tablists) move focus between tabs without activating them.
Home moves focus to the first tab; End moves focus to the last tab.
Enter or Space activates the focused tab, showing its associated panel.
Delete (optional) removes a closable tab.
Required ARIA
role="tablist"on the container, witharia-orientation="horizontal"oraria-orientation="vertical"as appropriate.role="tab"on each tab, witharia-selected="true"on the active tab andaria-selected="false"on the rest.aria-controlson each tab pointing at its panel'sid.role="tabpanel"on each panel, witharia-labelledbypointing at its owning tab'sid.Only the active tab is in the page tab sequence (
tabindex="0"); inactive tabs are removed withtabindex="-1".
Source: https://www.w3.org/WAI/ARIA/apg/patterns/tabs/examples/tabs-manual/
pattern-tabs-automatic-activation
Tabs (Automatic Activation) is the variant where moving keyboard focus along the tablist immediately changes which panel is visible. Pressing Left or Right (or Up or Down for a vertical tablist) both moves focus and activates the newly focused tab. This is the recommended activation mode when revealing a panel is cheap and the user benefits from a fast scan across panels.
Required keyboard
Tab moves focus to the active tab when entering the tablist, and out of the tablist on the next Tab press.
Left and Right arrows (horizontal) or Up and Down arrows (vertical) move focus and activate the newly focused tab in a single step.
Home moves focus to and activates the first tab; End moves focus to and activates the last tab.
Delete (optional) removes a closable tab.
Required ARIA
role="tablist"on the container, witharia-orientationset appropriately.role="tab"on each tab, witharia-selected="true"on the currently focused and active tab.aria-controlson each tab pointing at its panel'sid.role="tabpanel"on each panel, witharia-labelledbypointing at its owning tab'sid.Only the active tab is in the page tab sequence (
tabindex="0"); inactive tabs usetabindex="-1".
Source: https://www.w3.org/WAI/ARIA/apg/patterns/tabs/examples/tabs-automatic/
ARIA APG family — Treegrid
The Treegrid family covers hierarchical navigation widgets: simple disclosure trees, treegrids that overlay a hierarchy on a tabular layout, and the link pattern that often participates in tree navigation. APG groups the link pattern here because navigation trees commonly delegate row activation to nested links and because the link contract is small enough that it does not warrant a family of its own.
Source: W3C WAI-ARIA Authoring Practices Guide, Treegrid and related patterns, https://www.w3.org/WAI/ARIA/apg/patterns/treegrid/ and https://www.w3.org/WAI/ARIA/apg/patterns/treeview/.
pattern-tree-view
Tree View presents a hierarchical list of items where each item can have a parent and any number of children. Users navigate the tree with the arrow keys, expand and collapse parent nodes with the Right and Left arrows, and select an item with Enter or Space depending on the tree's selection model.
Required keyboard
Up and Down arrows move focus to the previous and next visible node in document order, descending into children of expanded nodes.
Right arrow on a collapsed parent expands it; on an expanded parent it moves focus to the first child; on a leaf it does nothing.
Left arrow on an expanded parent collapses it; on a collapsed or leaf node it moves focus to the parent.
Home moves focus to the first node; End moves focus to the last visible node.
Asterisk (optional) expands all sibling nodes at the same level as the focused node.
Printable characters jump focus to the next node whose visible label starts with the typed string.
Required ARIA
role="tree"on the outer container.role="treeitem"on each node.aria-expanded="true"oraria-expanded="false"on parent nodes; the attribute is omitted on leaf nodes.aria-levelindicates depth (1 for the root level, 2 for the first level of children, and so on).aria-posinsetandaria-setsizeindicate position within the parent's child list.aria-selectedindicates selection state when the tree supports selection.Only one node has
tabindex="0"(the focused or last-focused node); all other nodes usetabindex="-1".
Source: https://www.w3.org/WAI/ARIA/apg/patterns/treeview/
pattern-treegrid-email-client
Treegrid (Email Client) is APG's reference implementation of the treegrid pattern, modelled on an email-thread reader. The widget combines tabular row and cell navigation with the hierarchical expand and collapse behaviour of a tree, so users can drill into nested message threads while still inspecting per-column data such as sender, subject, and date.
Required keyboard
Right arrow on a collapsed row expands it; on an expanded row it moves focus to the first cell or to the first child row depending on the column.
Left arrow on an expanded row collapses it; on a collapsed row or a non-parent row it moves focus to the parent row.
Down and Up arrows move focus between rows (or cells, when focus is in a cell).
Home and End move focus to the first or last cell in the current row; Ctrl+Home and Ctrl+End move focus to the first or last cell in the grid.
Space toggles selection on the focused row when the treegrid supports row selection.
Required ARIA
role="treegrid"on the outer container.role="row"on each row, witharia-level,aria-posinset, andaria-setsizereflecting hierarchical position.aria-expanded="true"oraria-expanded="false"on parent rows.role="gridcell"orrole="columnheader"on cells;role="rowheader"on the row's primary cell when applicable.aria-selectedreflects per-row selection state.aria-controlson the row may point at the panel that displays the row's expanded content when applicable.
Source: https://www.w3.org/WAI/ARIA/apg/patterns/treegrid/examples/treegrid-1/
pattern-link
Link is the navigational primitive. APG advises authors to use the native HTML <a href="..."> element whenever possible because it brings the keyboard contract, the focusable behaviour, and the screen-reader semantics for free. The role="link" attribute exists only for cases where the native element cannot be used (for instance, when scripting must intercept activation).
Required keyboard
Tab moves focus to and away from the link.
Enter activates the link (follows the destination).
Space activates the link only when the element uses
role="link"; the native<a>element does not respond to Space and that behaviour is intentional.
Required ARIA
Prefer the native
<a href="...">element. Userole="link"only when the host element is not an anchor.aria-current="page"(or"step","location","date","time", or"true") indicates the current item within a navigation set.aria-disabled="true"indicates a link whose action is currently unavailable; the link must remain focusable so that users can discover the disabled state.aria-labelsupplies an accessible name when the visible link text is insufficient (icon-only or short labels).aria-expandedis set on a link that controls disclosure of a related region, although that combination usually indicates the element should be a button instead.
Requirements and compatibility
Written for: GitHub Copilot, Claude Code
Works with: Claude Code, Claude apps, OpenAI Codex, GitHub Copilot, Cursor, Gemini CLI, Kiro, OpenCode, Goose, Amp, Roo Code, JetBrains Junie, Factory Droid, Devin (ex-Windsurf), Mistral Vibe, OpenHands, Letta
“Works with” means the tool reads the open Agent Skills format; a skill written for one tool may use features others lack.
Needs: Claude Code frontmatter, Needs Python, Needs Node.js
The author says: Requires Python 3.11+ and uv; the scanner additionally needs Node.js and network access to fetch the pinned axe-core CLI from the public npm registry.
Security
What our scanner noticed· Low risk
- Instructions that use the network
- Scripts that your agent may run
This skill includes scripts that your agent may run. Read them before you use it.
A static scan is not a guarantee. Skills are third-party content: review a skill before you let an agent use it.
Attribution and licence
- Published by
- Microsoft
- Publisher
- Microsoft (Official)
- Source
- microsoft/hve-core · .github/skills/accessibility/accessibility · commit f425e94
- Licence
- MIT License (redistribution allowed)
- LICENSE (added by AutoGineer from LICENSE)
- ATTRIBUTION.md (written by AutoGineer)
- Synced
AutoGineer is not affiliated with or endorsed by Microsoft.
Get it from the source
1 of this skill’s files are not mirrored here, so there is no ZIP. Get the complete skill from its source: microsoft/hve-core