useChatAccessibility
Create the AI SDK observer and completion callbacks before calling useChat in the same React component.
Start here
For an existing compatible React + AI SDK app:
npm install @generative-a11y/react @generative-a11y/ai-sdkSee the integration guide for peer versions,
provider placement, preserved host options, and the preconstructed Chat case.
The root observer alone does not provide browser delivery.
Complete useChat integration
Call useChatAccessibility first, pass chatCallbacks into useChat, then observe the documented snapshot.
"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";
import { useState } from "react";
function Chat() {
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 });
const [input, setInput] = useState("");
// This is example host UI. Keep your existing message list and composer.
return (
<form
onSubmit={(event) => {
event.preventDefault();
if (!input.trim()) return;
void chat.sendMessage({ text: input });
setInput("");
}}
>
{chat.messages.map((message) => (
<p key={message.id}>
{message.parts
.map((part) => (part.type === "text" ? part.text : ""))
.join("")}
</p>
))}
<label>
Message
<input
value={input}
onChange={(event) => setInput(event.target.value)}
/>
</label>
<button
type="submit"
disabled={chat.status === "streaming" || chat.status === "submitted"}
>
Send
</button>
</form>
);
}
export function App() {
return (
<A11yProvider>
<Chat />
</A11yProvider>
);
}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.
const accessibility = useChatAccessibility({
runtime,
scopeId: "support",
onFinish,
onError,
});
const chat = useChat({ ...existingOptions, ...accessibility.chatCallbacks });Options
Pass these options to useChatAccessibility. The provider supplies runtime;
keep scopeId stable for the mounted chat.
Prop
Type
useObserveChatAccessibility
useObserveChatAccessibility reads your ChatIntegration without disposing it.
Prop
Type
The example uses your existing AI SDK /api/chat endpoint. A11yProvider
owns the runtime and browser delivery; useChatAccessibility owns only its
observer. Both clean up on unmount. Keep your existing transport and UI.
Add host onFinish/onError callbacks to useChatAccessibility when needed;
it composes them with its own callbacks. Do not overwrite chatCallbacks
after spreading them into useChat.
Public declarations: @generative-a11y/ai-sdk/react
All exported values and types for this entry point. Import these names from @generative-a11y/ai-sdk/react; the signatures below are reference material, not a replacement for the ownership and lifecycle guidance above.
ChatIntegration — type
interface ChatIntegration<UI_MESSAGE extends UIMessage = UIMessage> {
readonly observer: ChatObserver;
readonly chatCallbacks: {
readonly onFinish: ChatOnFinishCallback<UI_MESSAGE>;
readonly onError: ChatOnErrorCallback;
};
}useChatAccessibility — value
/**
* Creates an observer and the callbacks that must be passed to `useChat()`.
* Invoke this hook before `useChat()` in the same component.
*/
declare function useChatAccessibility<UI_MESSAGE extends UIMessage>(options: UseChatAccessibilityOptions<UI_MESSAGE>): ChatIntegration<UI_MESSAGE>;UseChatAccessibilityOptions — type
interface UseChatAccessibilityOptions<UI_MESSAGE extends UIMessage = UIMessage> {
readonly runtime: Pick<Runtime, "dispatch">;
readonly scopeId: string;
/** Captured with the observer; replace its scope/runtime to change language. */
readonly copy?: AdapterCopy;
readonly maxTrackedEntities?: number;
readonly getToolLabel?: (context: ToolLabelContext) => string;
readonly onFinish?: ChatOnFinishCallback<UI_MESSAGE>;
readonly onError?: ChatOnErrorCallback;
}UseChatSnapshot — type
/** The documented public state returned from `useChat()` that this hook reads. */
type UseChatSnapshot<UI_MESSAGE extends UIMessage = UIMessage> = Pick<UseChatHelpers<UI_MESSAGE>, "messages" | "status" | "error">;useObserveChatAccessibility — value
/**
* Observes a public `useChat()` snapshot after `useChat()` has been invoked.
* It borrows the integration and deliberately never disposes its observer.
*/
declare function useObserveChatAccessibility<UI_MESSAGE extends UIMessage>(options: UseObserveChatAccessibilityOptions<UI_MESSAGE>): void;UseObserveChatAccessibilityOptions — type
interface UseObserveChatAccessibilityOptions<UI_MESSAGE extends UIMessage = UIMessage> {
readonly integration: ChatIntegration<UI_MESSAGE>;
readonly snapshot: UseChatSnapshot<UI_MESSAGE>;
}