generative-a11y
Integrations

assistant-ui accessibility

Add paced screen-reader updates to an assistant-ui thread without replaying messages already present in its history.

Install the assistant-ui adapter

Terminal
npm install @generative-a11y/core @generative-a11y/dom @generative-a11y/assistant-ui

Use the thread your app already owns. See Compatibility for supported host dependencies.

Connect an existing thread runtime

Create the binding

Add this function to your browser app. It creates a runtime and browser delivery, then subscribes to your existing thread.

accessibility/assistant-ui.ts
import { createRuntime } from "@generative-a11y/core";
import { bindRuntime } from "@generative-a11y/dom";
import { bindThread, type ThreadRuntimeSource } from "@generative-a11y/assistant-ui";

export function connectAccessibility(thread: ThreadRuntimeSource) {
  const runtime = createRuntime({});
  const delivery = bindRuntime(runtime);
  const binding = bindThread({
    runtime,
    scopeId: "support-thread",
    thread,
  });

  return () => {
    binding.dispose();
    delivery.dispose();
    runtime.dispose();
  };
}

Connect it to your app lifecycle

Call this once when your hydrated thread is ready. Here, thread is your existing public assistant-ui thread runtime.

App mount callback
import { connectAccessibility } from "./accessibility/assistant-ui";

const disconnectAccessibility = connectAccessibility(thread);

Keep the binding alive across responses. Call the returned function when the surface unmounts or before replacing the thread:

App unmount callback
disconnectAccessibility();

Keep one owner for each resource

The example owns its core runtime and DOM delivery. If your app already has these, pass that runtime directly to bindThread and clean up only its returned binding. In React, use the runtime from A11yProvider and return () => binding.dispose() from your effect. Do not attach a second delivery layer to that provider.

Keep scopeId stable and unique for each mounted thread. The adapter leaves your visible UI and actions with the host application.

Optional configuration

maxTrackedEntities defaults to 1000 and bounds adapter identity records. copy supplies localized tool and interaction labels. See the bindThread reference for all options and the localization guide for copy configuration.

Compatibility

DependencySupported rangeChecked version
Node.js22+—
@assistant-ui/core>=0.3.13 <0.4.00.3.15

Your existing app supplies the host dependency. The adapter itself needs no React rendering layer; this example uses core and DOM directly.

Lifecycle mapping

Public thread evidenceReports
Append-only assistant textResponse text updates
Known complete or incomplete statusesCompletion, cancellation, or failure
Tool calls, results, approval IDs, and sourcesTool, approval, and citation updates

bindThread reads only getState and subscribe. It records the initial snapshot as history, so messages already present are not replayed. Connect after history hydration to establish the intended baseline.

Expected screen-reader behavior

Core paces announcement intents; DOM writes them to hidden live regions. Routine streaming and status changes leave focus in place. Confirm spoken output with your supported screen readers and browsers: a DOM transcript records browser writes, not speech.

What public state cannot prove

General retries, connection changes, runs, steps, and run hierarchy are unavailable. Replay fidelity is partial. The adapter does not infer these events from rendering or private framework state.

Troubleshooting

  • History is announced: bind after hydration and keep the thread identity and scopeId stable.
  • Repeated updates: check that only one binding observes the thread.
  • Missing updates: verify the public thread state and supported dependency range before changing announcement policy.