Accessibility test toolkit

TypeScript API

Import the screen reader test API from a11ied/test, the Vitest matchers and the queued sr fixture from a11ied/vitest, and driver sessions, axe runs, WCAG lookups, applicability, and host diagnostics from the a11ied package. These are the same functions the CLI commands and the MCP tools call.

Install

Install into a project

npm install a11ied

The package ships ESM with type declarations and four entry points: a11ied, a11ied/test, a11ied/vitest, and a11ied/browser. Internal file paths are not part of the public API and change between releases.

Errors

Failures throw CliUsageError for bad input, CliEnvironmentError for a missing browser or target, and DriverCommandError for an unresolvable command name. All three are exported, so instanceof works.

Tests

Drive a screen reader from a test

import { queuedScreenReader } from 'a11ied/test';

await using sr = await queuedScreenReader({ url: 'http://localhost:3000/checkout' });

sr.goTo({ role: 'button', name: 'Pay' });
sr.activate();
sr.expectSpoken(/order placed/iu);

Native sessions default to guarded. Commands check the observed foreground target, and typing checks between characters. Read the fresh current item, focus observations, and transcript after each action. If focus changes, refocus deliberately and inspect state before continuing. require-binding is an optional strict mode that refuses unavailable native binding. development bypasses target checks. Foreground checks do not guarantee that focus cannot change during dispatch.

screenReader(options?)Takes nativeInput, sr, url or html, mode (in-process by default, or broker), engine, page (in-process virtual browser only), recordingPath, idleTimeoutMinutes, and timeoutMs. Returns a ScreenReader with the page open. Every method returns a promise.
queuedScreenReader(options?, queue?), queueScreenReader(reader, queue?)
The same options, and a QueuedScreenReader instead. queueScreenReader wraps a ScreenReader that is already started.
QueuedScreenReaderEvery ScreenReader method except stop, with the same parameters, returning a Queued<T> thenable instead of a Promise<T>. A call queues a command and returns at once, and the queued commands run one at a time in the order they were queued. Awaiting a call runs the commands queued before it, then that one, and gives its value. session is a plain property, reader is the underlying ScreenReader, drain() waits for every queued command, and stop() drains, stops the reader even when the drain failed, then rethrows that error. await using calls stop().
CommandQueueOptionsThe second argument to both. track(settled) receives one promise per command, which rejects only when the command failed and nothing awaited it. signal, an AbortSignal, skips every command not yet started once it aborts. The a11ied/vitest fixture passes both, and a test needs neither.
ScreenReadernext, previous, press, type, read, title, find, goTo, elements, readAll, walk, wait, checkpoint, transcript, open, interact, stopInteracting, activate, top, bottom, escape, table, perform, screenshot, focus, expectSpoken, expectCursorOn, expectSpokenInOrder, stop, and Symbol.asyncDispose. Navigation methods return the phrase, and read returns a DriverCurrentItem.
ScreenReaderAssertionErrorThrown by expectSpoken, expectCursorOn, expectSpokenInOrder, wait, find, and goTo. Has expected, phrases, and exitCode: 4.
a11ied/vitestRe-exports the above, installs toHaveSpoken, toHaveSpokenInOrder, and toHaveCursorOn on expect, and exports a test whose sr fixture is a QueuedScreenReader, started from srOptions and stopped after each test. It needs Vitest 4.0.0 or later.
a11ied/browserFor Vitest browser mode: screenReader({ container }) and queuedScreenReader({ container }) over the mounted component, the same matchers, and the same queued sr fixture.

await using stops the reader when the block ends, so no try and finally is needed. On a queued reader it also runs the commands still in the queue. The Vitest guide has a test file to copy and the browser mode configuration.

Driver

Start and drive a session

import {
  attachDocumentToDriverSession,
  resolveDocumentTarget,
  runDriverSessionAction,
  startDriverSession,
  stopDriverSession,
} from 'a11ied';

const url = 'http://localhost:3000/checkout';
const target = await resolveDocumentTarget({ url });

await startDriverSession({ target: 'virtual' });
await attachDocumentToDriverSession({ html: await target.readHtml(), url });

try {
  const result = await runDriverSessionAction({ action: 'next', payload: { kind: 'heading' } });

  console.log(result.state.lastSpokenPhrase);
  console.log(result.state.transcript);
} finally {
  await stopDriverSession();
}

startDriverSession's url option only records where the session came from. It does not fetch a page or open a browser for you.

For a virtual session, call attachDocumentToDriverSession with the markup, as shown above. For a real target, open the page yourself first, since VoiceOver and NVDA read whatever is already on screen. a1 sr start and screenReader from a11ied/test both do this work for you.

startDriverSession(options?)Takes nativeInput, target, mode, engine, page (in-process virtual browser only), recordingPath, url, app, idleTimeoutMinutes, and timeoutMs. Returns the new session and, when one was already running, the replacedSession it stopped.
runDriverSessionAction(request, options?)The request is a DriverActionRequestInput, typed per action: { action: 'next' }, { action: 'press', payload: { keys: ['Tab', 'Tab'] } }, { action: 'goto', payload: { role: 'button' } }. A payload of the wrong shape does not compile, and payload fields with defaults, such as max, can be left out.
getDriverSessionStatus(options?)Read the session state without moving the cursor.
attachDocumentToDriverSession(document, options?)Point the session at another page.
runEphemeralDriverAction(options)One action in a session that is torn down before the call returns.
stopDriverSession(options?)Stop the driver, finish any recording, and delete the state file.
getActiveDriverSession()The active session, or undefined when the broker behind it is gone.
buildDriverTranscript(session, entries)Build the transcript document. formatTranscript renders it, and writeDriverTranscript writes it to a file.

These are the functions the CLI runs. Calls use the active session by default. stopDriverSession can select a retained recovery owner with options.sessionId. A session started with mode: 'broker' lives in its own process until stopped or until idleTimeoutMinutes, which defaults to 30.

One in-process JSDOM reader can own the shared document at a time. A conflicting session fails with virtual-session-conflict before changing the document. Stop the owner, or use a separate process or browser engine. An ephemeral JSDOM action follows the same rule.

A test should use screenReader from a11ied/test instead: it owns the lifecycle and disposes the session with the test.

Every action result includes session.engine for a virtual session and state.transcript: an array of entries with an index, an ISO at timestamp, the phrase, the itemText when the reader reported one, and a checkpoint label on the entries checkpoint added.

axe

Run axe against a URL

runAxe runs axe-core (axe), an open-source rules engine that checks a page against a set of automated accessibility rules, and returns the raw result.

import { runAxe } from 'a11ied';

const result = await runAxe(
  { kind: 'goto', url: 'http://localhost:3000/checkout' },
  { wcagVersion: '2.2', level: 'AA' },
);

for (const violation of result.violations) {
  console.log(violation.id, violation.impact, violation.nodes[0]?.target);
}

The first argument is a DocumentLoad: { kind: 'goto', url } for a page Playwright navigates to, or { kind: 'html', html } for inline markup. resolveDocumentTarget builds one from a URL, a file path, - for stdin, or { html }, the same way the CLI resolves its positional target.

Scope the run with level, criterion, or ruleIds. The options type allows one of the three. Omit all three to run every rule mapped in the WCAG version.

The result has violations, passes, incomplete, inapplicable, the resolved ruleIds, and a selection object that says how the rule list was chosen.

Browser state

Inspect the page the reader is using

withLoadedPage(load, callback, options?) keeps a browser page open during the callback. withInteractiveBrowserPage(url, callback, options?) uses a headed browser for interactive sign-in and navigation. Both accept the existing storage, cookies, headers, viewport, click, and wait options.

import { runAxe, withInteractiveBrowserPage } from 'a11ied';
import { screenReader } from 'a11ied/test';

await withInteractiveBrowserPage('https://createdbyfireside.com/', async (page) => {
   await using sr = await screenReader({ page });
   await sr.next('heading');
   return runAxe(
      { kind: 'goto', url: page.url() },
      { wcagVersion: '2.2', level: 'AA', page },
   );
});

Complete sign-in or open the interaction state inside the callback before collecting observations. Pass the same page to runAxe, buildAuditReport, and the core tree, title, and HTML readers. Screenshots use that page's Playwright API. These reads preserve the document, cookies, and session storage. A new scan always inspects the current UI.

An existing page rejects replacement setup and a different requested URL. Closed pages or main-document navigation during observation reject the result. Stopping the reader leaves the caller's page open. A second virtual reader on that page fails with virtual-session-conflict until the owner stops.

The page reader option supports the in-process virtual browser engine. Broker sessions and native readers cannot receive a Playwright page. This example verifies simulated behavior; it does not establish VoiceOver or NVDA evidence or complete WCAG coverage.

Browser virtual activation clicks the exact DOM element selected by the simulated reader. Playwright checks visibility and enabled state, scrolls the element into view, and moves the mouse before clicking. This can trigger hover behavior and differs from the JSDOM reader's simulated click. Activation waits for initiated navigation and never retries a detached target or an uncertain click. Same-document and hash changes preserve the reader state.

Standards

Look up criteria and coverage

These functions read the Web Content Accessibility Guidelines (WCAG), the standard most accessibility requirements come from, one success criterion at a time. A success criterion is a numbered, testable requirement such as 4.1.3, assigned a conformance level of A, AA, or AAA.

import {
  listWcagCriteria,
  searchWcagCriteria,
  showWcagTestMethod,
  showWcagCriterion,
} from 'a11ied';

const status = showWcagCriterion('4.1.3', '2.2');

console.log(status.criterion.title, status.testMethod.method);

const { testMethod, strategy } = showWcagTestMethod('4.1.3', '2.2');

if (strategy.requiresRealTarget) {
  console.log('Needs a driver session:', strategy.procedureIds);
}

const levelAA = listWcagCriteria('AA', '2.2');
const matches = searchWcagCriteria('focus order', { version: '2.2', limit: 3 });

These functions read data bundled with the package, so they make no network request, except showWcagAxeRule, the one asynchronous lookup.

showWcagCriterionReturns the criterion with its coverage and strategy.
listWcagCriteriaTakes undefined as the level to return every criterion.
showWcagTechnique('G18', '2.2')Resolves a technique or failure ID to the criteria that list it.
showWcagCoverageSummary('2.2')Returns the coverage totals per level.
showWcagAxeRule('color-contrast', '2.2')Maps an axe rule ID to its criteria, and reads the help text from the installed axe-core.

Relevant criteria

import { inspectRelevantCriteriaUrl, inspectCriterionUrl } from 'a11ied';

const matrix = await inspectRelevantCriteriaUrl('http://localhost:3000', '2.2');
const focusOrder = await inspectCriterionUrl('2.4.3', 'http://localhost:3000', '2.2');

console.log(focusOrder.assessment.state);
console.log(focusOrder.assessment.reasons);

Both fetch the URL and parse the returned HTML. Use inspectRelevantCriteriaTarget and inspectCriterionTarget when you already have a resolved document target rather than a URL.

Runtime

Check the host

import { createDoctorReport, renderDoctorText, resolveAvailableDefaultTarget } from 'a11ied';

const report = createDoctorReport();
const stepsLeft = report.actions.filter((action) => { return action.required; });

console.log(report.ready, stepsLeft);
console.log(renderDoctorText(report));

const { target, targetType, warning } = await resolveAvailableDefaultTarget();

resolveAvailableDefaultTarget checks whether the platform screen reader is ready for automation and falls back to virtual when it is not. The returned warning is set on every fallback.

Assessment lifecycle

A host agent uses these shared operations to coordinate a desktop WCAG assessment. The coordinator stores the work and validates proof. The agent performs the procedure, captures observations, and evaluates the result against the procedure's WCAG requirements.

startAuditAssessment(options)Create a durable run with a target, WCAG version and level, scope, and observed environment capabilities. An existing run file is never replaced.
executeAuditAssessment(request)Use the same validated start, state, journey, queue, next, status, resume, evaluate, block, and finalize operations as CLI and MCP. Status lists include totals and nextOffset; offset defaults to 0 and limit to 20, with a maximum of 100.
registerAuditState(input)Record the observed target, state fingerprint, environment, setup, and artifact references. Identical observations preserve their original timestamp. Changed observations invalidate affected proof while retaining a live claim until recovery.
registerAuditJourney(input)Record ordered states for a process that may span pages. Its assessment progress comes from validated checks. A saved completed flag cannot establish coverage.
nextAuditAssessment(file)Claim one required check and return its procedure, including setup, actions, evidence requirements, evaluation rules, and recovery instructions. Concurrent callers cannot claim a second check. Unsupported capabilities remain visible.
recordEvidence(record, options)Validate and save traceable observations and artifacts. Pass the run file and its evidence file. Provenance and non-image envelopes use attempt from the claimed check's attempts. Save the returned evidence ID for the result transition.
transitionAssessmentCheck(input)Complete the claimed check with an outcome and saved evidence IDs, or record a specific blocker. Completion rejects missing, mismatched, conflicting, and obsolete proof.
getAuditAssessmentStatus(file)Revalidate catalog obligations, saved artifacts, and discovery scope. Returns global coverage, progress for states, pages, and journeys, and explicit unresolved issues.
resumeAuditAssessment(input)Keep interrupted work blocked without replaying actions. After inspecting saved proof and restoring a safe state, supply retryCheckIds in a separate call. Invalidated proof may be retried; valid completed checks cannot be replayed.
finalizeAuditAssessment(input)Complete only a fully covered run. allowPartial preserves an active run and its unresolved issues for partial reporting. Discovery page flags and inconclusive outcomes cannot satisfy completion.

These operations perform no target input themselves. Real reader evidence still requires a runtime receipt that verifies the target and session binding. Current native binding limitations remain unresolved; virtual or mocked checks cannot establish a complete native audit.

Exports

Export map

Testsa11ied/test: screenReader, queuedScreenReader, queueScreenReader, ScreenReader, ScreenReaderAssertionError. a11ied/vitest and a11ied/browser: test, it, screenReaderMatchers
SessionsstartDriverSession, getDriverSessionStatus, runDriverSessionAction, runEphemeralDriverAction, stopDriverSession, attachDocumentToDriverSession, cleanupStaleDriverSessions
CommandslistDriverCommands, resolveDriverCommand, driverCommandSets, isDriverCommandSet, parseDriverCommandSet
axerunAxe
WCAGlistWcagCriteria, showWcagCriterion, searchWcagCriteria, showWcagTestMethod, showWcagTestMethodSummary, showWcagTechnique, showWcagAxeRule
Relevant criteriainspectRelevantCriteriaTarget, inspectRelevantCriteriaUrl, inspectCriterionTarget, inspectCriterionUrl
Page and screen reader selectionresolveDocumentTarget, openUrlInSystemAutomationBrowser, resolveDefaultTarget, resolveAvailableDefaultTarget, resolveTargetType, listSupportedTargets
DiagnosticscreateDoctorReport, renderDoctorText, listCliCommands
ErrorsCliUsageError, CliEnvironmentError, DriverCommandError

The package also exports the option and result types, including AxeRunOptions, ScreenReaderOptions, QueuedScreenReader, CommandQueueOptions, StartDriverSessionOptions, DriverCommandList, and TargetType.

Packages

The @a11ied packages

a11ied re-exports the public core helpers and is the package to depend on. Install a scoped package only when you need a lower-level API it does not re-export.

@a11ied/contractsZod schemas and result types shared across the runtime.
@a11ied/coreWCAG lookup, target resolution, driver orchestration, axe runs, doctor reports.
@a11ied/guidepupPlatform adapters for VoiceOver, NVDA, and the virtual reader.
@a11ied/wcag-dataUpstream data sync and the generated artifacts.
@a11ied/wcag-engineLookup, search, coverage, and applicability over those artifacts.
@a11ied/mcp-serverThe MCP server entrypoint.