generative-a11y
Integrations

Vercel AI SDK accessibility

Add paced screen-reader updates to AI SDK through documented state, finish callbacks, and error callbacks.

Install for your existing app

Terminal
npm install @generative-a11y/react @generative-a11y/ai-sdk

Requires AI SDK 7 / @ai-sdk/react 4 and React 18.2+ or 19. See Compatibility for the supported minor versions and peers.

React integration

Import the hooks

Add these imports to your chat module. In a React Server Components app, keep it behind a "use client" boundary.

components/chat.tsx
"use client";

import { DefaultChatTransport } from "ai";
import { useChat } from "@ai-sdk/react";
import { A11yProvider, useRuntime } from "@generative-a11y/react";
import {
  useChatAccessibility,
  useObserveChatAccessibility,
} from "@generative-a11y/ai-sdk/react";

Connect your chat

Inside your existing Chat(), create accessibility before useChat, then observe the returned state. Keep your transport and other chat options; the example below uses /api/chat.

components/chat.tsx
const runtime = useRuntime();
const accessibility = useChatAccessibility({
  runtime,
  scopeId: "support",
});
const chat = useChat({
  id: "support",
  transport: new DefaultChatTransport({ api: "/api/chat" }),
  ...accessibility.chatCallbacks,
});
useObserveChatAccessibility({ integration: accessibility, snapshot: chat });

Add the provider

Wrap your chat once:

components/chat.tsx
export function App() {
  return (
    <A11yProvider>
      <Chat />
    </A11yProvider>
  );
}

Keep using chat in your existing UI. See the complete copyable component for a minimal message list and composer.

Existing callbacks

Already use onFinish or onError? Pass your existing functions to useChatAccessibility so they are composed rather than replaced. Keep the rest of your options, including transport, in existingOptions, and spread the composed callbacks last. Omit either callback if your app does not use it.

components/chat.tsx
const accessibility = useChatAccessibility({
  runtime,
  scopeId: "support",
  onFinish,
  onError,
});
const chat = useChat({ ...existingOptions, ...accessibility.chatCallbacks });

Ownership and behavior

One provider per chat surface

A11yProvider owns runtime and browser delivery. The adapter owns observer cleanup. Both clean up on unmount; keep the provider mounted across responses and scopeId stable for that session.

This adds paced live-region updates without changing your visible UI or moving focus during ordinary streaming. Your app still owns semantic structure, keyboard controls, and accessible content. DOM checks do not prove screen-reader output.

Advanced: preconstructed Chat

With useChat({ chat: existingChat }), other initialization options, including callbacks, are ignored. Compose callbacks when constructing that chat using the observer API, and manage its subscription and cleanup at that boundary.

Compatibility

DependencySupported rangeChecked version
Node.js22+22.16.0
ai>=7.0.0 <7.1.07.0.77
@ai-sdk/react>=4.0.0 <4.1.04.0.80
react / react-dom18.2+ within major 18, or 19.x19.2.8
zod (host SDK peer)3.25.76+ within major 3, or 4.1.8+ within major 44.4.3

Your existing AI SDK app supplies these dependencies and a working backend. Core and DOM install transitively; declare them directly only if your own code imports them. The React hooks live at @generative-a11y/ai-sdk/react; the root observer entry remains usable without React.

Lifecycle mapping

The adapter translates confirmed public SDK evidence:

EvidenceReports
Append-only assistant textResponse text updates
Tool parts, approval IDs, and source IDsTools, approvals, and citations
Composed onFinish / onError callbacksCompletion, cancellation, failure, and connection loss

Retry and regenerate detection are unavailable. A successful finish after a disconnection reports recovery. See the observer reference for exact mappings and limits.

Troubleshooting

  • Missing completion updates: create accessibility before useChat and spread its composed callbacks last.
  • Repeated output: keep scopeId stable and attach only one observer per chat.
  • Existing messages are silent: the first snapshot establishes a history baseline. The adapter observes subsequent updates without replaying history.