Expert guidance for building chat UIs with AI SDK React hooks. Use when: (1) Building chatbots with useChat hook, (2) Implementing tool UIs with server/client execution, (3) Handling message persistence and stream resumption, (4) Creating generative UI with React components, (5) Integrating with Next.js/Node/Fastify/Nest backends, (6) Using useObject for streaming structured data. Triggers: useChat, useObject, useCompletion, chat UI, chatbot, generative UI, message persistence, @ai-sdk/react, UIMessage, tool parts, streaming UI, toUIMessageStreamResponse.
Installation
Details
Usage
After installing, this skill will be available to your AI coding assistant.
Verify installation:
npx agent-skills-cli listSkill Instructions
name: ai-sdk-ui description: | Expert guidance for building chat UIs with AI SDK React hooks. Use when: (1) Building chatbots with useChat hook, (2) Implementing tool UIs with server/client execution, (3) Handling message persistence and stream resumption, (4) Creating generative UI with React components, (5) Integrating with Next.js/Node/Fastify/Nest backends, (6) Using useObject for streaming structured data. Triggers: useChat, useObject, useCompletion, chat UI, chatbot, generative UI, message persistence, @ai-sdk/react, UIMessage, tool parts, streaming UI, toUIMessageStreamResponse.
AI SDK UI - Chat & Generative UI Framework
AI SDK UI provides framework-agnostic hooks for building interactive chat, completion, and assistant applications with real-time streaming and state management.
Quick Start: Basic Chat
Client (React)
'use client';
import { useChat } from '@ai-sdk/react';
import { DefaultChatTransport } from 'ai';
import { useState } from 'react';
export default function Chat() {
const { messages, sendMessage, status } = useChat({
transport: new DefaultChatTransport({ api: '/api/chat' }),
});
const [input, setInput] = useState('');
return (
<>
{messages.map(message => (
<div key={message.id}>
{message.role === 'user' ? 'User: ' : 'AI: '}
{message.parts.map((part, index) =>
part.type === 'text' ? <span key={index}>{part.text}</span> : null
)}
</div>
))}
<form onSubmit={e => {
e.preventDefault();
if (input.trim()) {
sendMessage({ text: input });
setInput('');
}
}}>
<input
value={input}
onChange={e => setInput(e.target.value)}
disabled={status !== 'ready'}
/>
<button type="submit" disabled={status !== 'ready'}>
Submit
</button>
</form>
</>
);
}
Server (Next.js App Router)
import { convertToModelMessages, streamText, UIMessage } from 'ai';
import { openai } from '@ai-sdk/openai';
export const maxDuration = 30;
export async function POST(req: Request) {
const { messages }: { messages: UIMessage[] } = await req.json();
const result = streamText({
model: openai('gpt-4o'),
system: 'You are a helpful assistant.',
messages: await convertToModelMessages(messages), // v6: now async
});
return result.toUIMessageStreamResponse();
}
Hook Selection Guide
| Hook | Use Case | Stream Type | Best For |
|---|---|---|---|
useChat | Multi-turn conversations | Messages with parts (text, tools, files) | Chatbots, assistants, tool-calling UIs |
useObject | Structured data streaming | Typed objects with Zod schema | Forms, dashboards, real-time data |
useCompletion | Single-turn text generation | Plain text | Autocomplete, simple generation |
Decision tree:
- Need conversation history + tools? →
useChat - Need typed/structured streaming data? →
useObject - Need simple text completion? →
useCompletion
Core Patterns
1. Status Management
const { status, stop } = useChat();
// status values: 'submitted' | 'streaming' | 'ready' | 'error'
{(status === 'submitted' || status === 'streaming') && (
<div>
{status === 'submitted' && <Spinner />}
<button onClick={() => stop()}>Stop</button>
</div>
)}
2. Error Handling
const { error, reload } = useChat();
{error && (
<>
<div>An error occurred.</div>
<button onClick={() => reload()}>Retry</button>
</>
)}
Server-side error messages:
return result.toUIMessageStreamResponse({
onError: error => {
if (error instanceof Error) return error.message;
return 'Unknown error';
},
});
3. Message Modification
const { messages, setMessages } = useChat();
const handleDelete = (id: string) => {
setMessages(messages.filter(m => m.id !== id));
};
const handleEdit = (id: string, newText: string) => {
setMessages(messages.map(m =>
m.id === id
? { ...m, parts: [{ type: 'text', text: newText }] }
: m
));
};
4. File Attachments
const [files, setFiles] = useState<FileList | undefined>();
<form onSubmit={e => {
e.preventDefault();
sendMessage({ text: input, files });
setFiles(undefined);
}}>
<input
type="file"
onChange={e => setFiles(e.target.files ?? undefined)}
multiple
/>
</form>
5. Custom Request Options
// Per-request customization (recommended)
sendMessage(
{ text: input },
{
headers: { Authorization: 'Bearer token' },
body: { temperature: 0.7, user_id: '123' },
metadata: { sessionId: 'abc' },
}
);
// Hook-level configuration
const { sendMessage } = useChat({
transport: new DefaultChatTransport({
api: '/api/chat',
headers: () => ({ Authorization: `Bearer ${getToken()}` }),
body: { systemContext: 'expert' },
}),
});
6. Message Metadata
// Server: Attach metadata
return result.toUIMessageStreamResponse({
messageMetadata: ({ part }) => {
if (part.type === 'start') {
return { createdAt: Date.now(), model: 'gpt-4o' };
}
if (part.type === 'finish') {
return { totalTokens: part.totalUsage.totalTokens };
}
},
});
// Client: Access metadata
{messages.map(m => (
<div key={m.id}>
{m.metadata?.createdAt && new Date(m.metadata.createdAt).toLocaleString()}
{m.parts.map(part => part.type === 'text' ? part.text : null)}
{m.metadata?.totalTokens && <span>{m.metadata.totalTokens} tokens</span>}
</div>
))}
7. Regenerate & Stop
const { regenerate, stop, status } = useChat();
<>
<button onClick={stop} disabled={status !== 'streaming'}>
Stop
</button>
<button onClick={regenerate} disabled={!(status === 'ready' || status === 'error')}>
Regenerate
</button>
</>
Message Parts Type Reference
Messages use a parts array instead of content for flexible multi-modal rendering:
type MessagePart =
| { type: 'text'; text: string }
| { type: 'file'; filename: string; mediaType: string; url: string }
| { type: 'tool-invocation'; toolName: string; input: unknown; result?: unknown }
| { type: 'tool-result'; toolName: string; result: unknown }
| { type: 'reasoning'; text: string } // DeepSeek R1, Claude 3.7 Sonnet
| { type: 'source-url'; id: string; url: string; title?: string } // Perplexity, Google
| { type: 'source-document'; id: string; title?: string };
// Render pattern
{message.parts.map((part, index) => {
switch (part.type) {
case 'text':
return <span key={index}>{part.text}</span>;
case 'file':
return part.mediaType.startsWith('image/')
? <img key={index} src={part.url} alt={part.filename} />
: null;
case 'reasoning':
return <pre key={index}>{part.text}</pre>;
case 'source-url':
return <a key={index} href={part.url}>{part.title ?? 'Source'}</a>;
case 'tool-invocation':
return <ToolUI key={index} tool={part} />;
default:
return null;
}
})}
Framework Support
| Framework | Package | Hooks |
|---|---|---|
| React | @ai-sdk/react | useChat, useCompletion, useObject |
| Vue.js | @ai-sdk/vue | useChat, useCompletion, useObject |
| Svelte | @ai-sdk/svelte | Chat, Completion, StructuredObject |
| Angular | @ai-sdk/angular | Chat, Completion, StructuredObject |
| SolidJS | ai-sdk-solid (community) | useChat, useCompletion, useObject |
AI Elements (shadcn/ui Components)
Pre-built UI components for chat interfaces: https://ai-sdk.dev/elements
Includes: Message bubbles, input fields, tool UIs, and more.
Reference Navigation
| Reference | Topics |
|---|---|
| usechat-fundamentals.md | Hook API, transport config, status lifecycle, message state |
| tool-integration.md | Tool calling, client/server execution, tool approval, type inference |
| generative-ui.md | React components in streams, dynamic UIs, RSC integration |
| persistence.md | Message storage, resume streams, optimistic updates, sync patterns |
| hooks-reference.md | Complete API for useChat/useObject/useCompletion, options reference |
| backend.md | Next.js/Node/Fastify/Nest routes, convertToModelMessages, toUIMessageStreamResponse |
| production.md | Error boundaries, retry strategies, throttling, security best practices |
| migration.md | v6 migration guide, breaking changes, codemod usage |
Event Callbacks
const { messages } = useChat({
onFinish: ({ message, messages, isAbort, isDisconnect, isError }) => {
// Log completion, update analytics, trigger side effects
if (!isError) logMessage(message);
},
onError: error => {
// Custom error handling, fallback UI
Sentry.captureException(error);
},
onData: data => {
// Process data parts, validate responses
// Throw error to abort processing
},
});
Advanced: Custom Transport
const { sendMessage } = useChat({
transport: new DefaultChatTransport({
prepareSendMessagesRequest: ({ id, messages, trigger, messageId }) => {
if (trigger === 'submit-user-message') {
return {
body: {
id,
message: messages[messages.length - 1],
messageId,
},
};
}
// Handle regenerate, custom triggers
},
}),
});
Type Inference for Tools
import { InferUITools, InferAgentUIMessage, ToolSet, UIMessage } from 'ai';
const tools = {
weather: {
description: 'Get weather',
inputSchema: z.object({ location: z.string() }),
execute: async ({ location }) => `Sunny in ${location}`,
},
} satisfies ToolSet;
type MyUITools = InferUITools<typeof tools>;
type MyUIMessage = UIMessage<never, never, MyUITools>;
// Or for agent messages:
// type MyAgentUIMessage = InferAgentUIMessage<typeof myAgent>;
const { messages } = useChat<MyUIMessage>();
Reasoning & Sources
// Enable reasoning (DeepSeek R1, Claude 3.7 Sonnet)
return result.toUIMessageStreamResponse({ sendReasoning: true });
// Enable sources (Perplexity, Google)
return result.toUIMessageStreamResponse({ sendSources: true });
// Render reasoning and sources
{message.parts.map(part => {
if (part.type === 'reasoning') return <pre>{part.text}</pre>;
if (part.type === 'source-url') return <a href={part.url}>{part.title}</a>;
})}
Performance: Throttle Updates
const { messages } = useChat({
experimental_throttle: 50, // React only: throttle to 50ms
});
Plain Text Streams
import { TextStreamChatTransport } from 'ai';
const { messages } = useChat({
transport: new TextStreamChatTransport({ api: '/api/chat' }),
});
Note: Tools, usage, and finish reasons unavailable with TextStreamChatTransport.
More by BjornMelin
View allExpert guidance for building AI agents with ToolLoopAgent (AI SDK v6+). Use when creating agents, configuring stopWhen/prepareStep, callOptionsSchema/prepareCall, dynamic tool selection, tool loops, or agent workflows (sequential, routing, evaluator-optimizer, orchestrator-worker). Triggers: ToolLoopAgent, agent loop, stopWhen, stepCountIs, prepareStep, callOptionsSchema, prepareCall, hasToolCall, InferAgentUIMessage, agent workflows.
Production-ready Supabase integration patterns for Next.js/React/TypeScript applications. Use when working with Supabase for (1) SSR authentication with @supabase/ssr, (2) Database operations and migrations, (3) Row Level Security (RLS) policies, (4) Storage buckets and file uploads, (5) Realtime channels and presence, (6) Edge Functions with Deno, (7) pgvector embeddings and semantic search, (8) Vercel deployment and connection pooling, (9) CLI operations (type generation, migrations). Triggers on Supabase client setup, auth patterns, RLS policies, storage uploads, realtime subscriptions, Edge Functions, vector search, or Vercel+Supabase deployment.
Expert guidance for building Dash applications with Dash Mantine Components (DMC) v2.x. Use when creating dashboards, forms, data visualization apps with DMC. Covers: MantineProvider theming, style props (m, p, c, bg, w, h), Styles API, callbacks (basic, pattern-matching ALL/MATCH/ALLSMALLER, clientside, background), multi-page apps with Dash Pages, charts (LineChart, BarChart, DonutChart), date pickers, modals, and all 100+ components. Triggers on: dash-mantine-components, DMC, MantineProvider, dmc.Button, dmc.Select, dmc.Modal, dmc.BarChart, Mantine theme, Dash UI components, Dash callbacks, multi-page Dash app, pattern-matching callbacks, clientside callbacks, AppShell.
World-class Vitest QA/test engineer for TypeScript + Next.js (local + CI performance focused)
