Chat - Minimal core chat
Start with the smallest working ChatProvider and useChat() setup.
This demo keeps the UI intentionally small to demonstrate the core pattern:
ChatProviderowns the runtime and wraps your component treeuseChat()reads messages and streaming state in one call- a plain input and button trigger
sendMessage() - the assistant response streams back through the adapter
Everything else — layout, styling, message rendering — is plain React with no framework opinions.
Key concepts
Defining an adapter
The adapter is the only required prop on ChatProvider.
At minimum, it implements sendMessage() and returns a ReadableStream of chunks:
const adapter: ChatAdapter = {
async sendMessage({ message }) {
return createChunkStream(
createTextResponseChunks(
`response-${message.id}`,
`You said: "${getMessageText(message)}".`,
),
{ delayMs: 220 },
);
},
};
Wiring ChatProvider
Wrap your component tree with ChatProvider and pass the adapter:
<ChatProvider adapter={adapter} initialActiveConversationId="support">
<MinimalChatInner />
</ChatProvider>
initialActiveConversationId sets the initial conversation without requiring controlled state.
Reading state with useChat()
Inside ChatProvider, call useChat() to get messages, streaming state, and actions:
const { messages, sendMessage, isStreaming } = useChat();
Minimal headless chat
Idle
Send the first message to start the thread.
Press Enter to start editing
Key takeaways
- The adapter is the only backend integration point — the runtime handles everything else
useChat()provides both state and actions in a single hook- No CSS, no components, no design system required — core is pure runtime
See also
- Hooks for the full hook API reference
- Adapters for writing real adapters
- Controlled state for owning state externally
- Selector-driven thread for efficient large threads