With the AI SDK, forward the model's reasoning to the client with sendReasoning: true on toUIMessageStream. It arrives in message.parts as reasoning parts with text and a state of streaming or done. Show a thinking line with a live timer while the reasoning streams, pass its paragraphs as short steps, and mark it done when the answer's first text part arrives, so it folds to "Thought for 12s".
How the AI SDK sends reasoning#
The Chatbot page's Reasoning section says some models send reasoning tokens before the answer and that you forward them with sendReasoning. On the client they are parts of type reasoning with a text property; the docs render them in a <pre>. The Reasoning page covers the provider side: which models return reasoning and how to ask for it.
Two things to plan for in the UI:
- No reasoning at all. Many models and settings return none. The thinking line should still show from the moment you send, so the wait has a label.
- Raw text is long. Reasoning can run to pages. A line that says what the model is doing, with the detail one click away, reads better than a wall of grey text.
From parts to the thinking line#
| Moment | AI SDK signal | Thinking state props |
|---|---|---|
| Request sent, nothing back | status === "submitted" |
label, startedAt |
| Reasoning streaming | A reasoning part with state: "streaming" |
steps from its paragraphs, the last one active |
| Reasoning done, answer starting | A text part appears |
done; the timer freezes and the line folds |
| Thread reloaded later | Parts from history | done with elapsedMs you stored |
| Model returns no reasoning | No reasoning part |
label and timer only, done when text arrives |
The code#
The route forwards reasoning. The model and its reasoning options come from the provider docs.
import { convertToModelMessages, createUIMessageStreamResponse, streamText, toUIMessageStream, type UIMessage } from "ai";
export async function POST(req: Request) {
const { messages }: { messages: UIMessage[] } = await req.json();
const result = streamText({ model: "openai/gpt-5.5", messages: await convertToModelMessages(messages) });
return createUIMessageStreamResponse({ stream: toUIMessageStream({ stream: result.stream, sendReasoning: true }) });
}The client turns the reasoning text into steps and folds the line when the answer begins.
"use client";
import type { UIMessage } from "ai";
import { ThinkingState, type ThinkingStateStep } from "@/components/pixilui/thinking-state";
// One step per paragraph, its first sentence only. The full text stays one click away in your own panel if you want it.
function stepsFrom(reasoning: string, streaming: boolean): ThinkingStateStep[] {
const paragraphs = reasoning.split(/\n{2,}/).map((p) => p.trim()).filter(Boolean);
return paragraphs.map((p, i) => ({
id: String(i),
text: (p.split(/(?<=[.!?])\s/)[0] ?? p).replace(/\*\*/g, "").slice(0, 90),
status: streaming && i === paragraphs.length - 1 ? "active" : "done",
}));
}
export function AssistantTurn({ message, startedAt }: { message: UIMessage; startedAt: number }) {
const reasoning = message.parts.filter((part) => part.type === "reasoning");
const answering = message.parts.some((part) => part.type === "text" && part.text.length > 0);
const streaming = reasoning.some((part) => part.state === "streaming");
const text = reasoning.map((part) => part.text).join("\n\n");
return (
<ThinkingState
label="Working through your question"
steps={stepsFrom(text, streaming && !answering)}
startedAt={startedAt}
done={answering}
doneLabel={(ms) => (ms === null ? "Thought it through" : `Thought for ${Math.max(1, Math.round(ms / 1000))}s`)}
/>
);
}Render AssistantTurn above the answer's text parts, with startedAt set when you called sendMessage. Install it with npx shadcn@latest add @pixilui/thinking-state.
What the thinking line adds#
- A label from the first moment. The line shows before any reasoning arrives, so a model that returns none still gets a labeled wait.
- A live timer that freezes.
startedAtcounts;donestops it and the line folds to "Thought for 12s". - Steps instead of a transcript. Short lines, the newest active, the rest checked.
- Reduced motion. No moving light, no rise: the steps and the label change at once.
When the framework already ships a UI#
The AI SDK renders reasoning as a <pre> in its docs and ships no component. AI Elements, Vercel's component set for the AI SDK, has Reasoning and Chain of Thought; its site says it moves into shadcn/ui at the end of November 2026. Keep Reasoning if your product shows the full reasoning text; the AI Elements migration doc maps it to the thinking line when you want a label, steps and a timer instead.
When not to use this#
- The reasoning is the product. A tutor or an audit tool that must show every line of the model's thinking needs a full text panel.
- Instant answers. A reply in under a second should not flash a thinking line; show it only after a short delay.
- Agent runs with tools. When the model calls tools between thoughts, a run timeline shows the steps better.
Questions#
How do I show reasoning tokens with the AI SDK?
Pass sendReasoning: true to toUIMessageStream on the server. On the client, reasoning arrives as reasoning parts in message.parts, each with text and a state.
How do I show "Thought for 12 seconds"?
Note when you sent the message, and mark the thinking line done when the first text part of the answer arrives. The elapsed time between the two is the thinking time; store it with the message to show it again after a reload.
Why are there no reasoning parts in my messages?
Either the model does not return reasoning, or the provider needs it switched on, or the route does not forward it. The AI SDK's Reasoning page covers the provider settings, and sendReasoning controls forwarding.
Related#
- How to show an AI model's reasoning or thinking state: what a thinking line needs, framework aside.
- Reasoning UI with the OpenAI Agents SDK: the same line from reasoning items.
- Streaming text with the Vercel AI SDK: the answer that follows the line.
Sources#
- AI SDK UI: Chatbot, Reasoning, read 4 November 2026
- AI SDK Core: Reasoning, read 4 November 2026
- ai on npm (version 7.0.162, its
ReasoningUIParttype), read 4 November 2026 - AI Elements, read 4 November 2026