import { ReasoningSample, ReasoningStreamingSample } from "@/components/pages/docs/samples/reasoning";
import { ReasoningCompletedSample } from "@/components/pages/docs/samples/reasoning/completed-with-duration";
import { ReasoningLongSample } from "@/components/pages/docs/samples/reasoning/long-reasoning";
import { ReasoningVariantsSample } from "@/components/pages/docs/samples/reasoning/variant-showcase";
Getting Started#
Add reasoning#
<InstallCommand shadcn={["reasoning"]} />
This adds a /components/assistant-ui/reasoning.tsx file to your project.
Use in your application#
Previously, reasoning parts were rendered via components.Reasoning and grouped via components.ReasoningGroup on MessagePrimitive.Parts. Both are deprecated; MessagePrimitive.GroupedParts is the supported replacement, and the highlighted lines below show the new pieces.
Render reasoning parts through MessagePrimitive.GroupedParts. Group consecutive reasoning parts with "group-reasoning", then compose ReasoningRoot, ReasoningTrigger, ReasoningContent, and ReasoningText around the grouped children.
While reasoning is streaming, part.status.type === "running". Pass that to streaming so the accordion auto-opens during streaming with a bottom-pinned live preview of the newest tokens, and permanently defers to the first manual toggle. streaming only holds the disclosure open; when it ends the accordion returns to defaultOpen, which is false by default and therefore collapses. Pass defaultOpen to keep reasoning expanded once the run finishes.
import { MessagePrimitive, groupPartByType } from "@assistant-ui/react";
import { MarkdownText } from "@/components/assistant-ui/markdown-text";
import { ToolFallback } from "@/components/assistant-ui/tool-fallback";
import {
Reasoning,
ReasoningContent,
ReasoningRoot,
ReasoningText,
ReasoningTrigger,
} from "@/components/assistant-ui/reasoning"; // [!code ++]
const AssistantMessage: FC = () => {
return (
<MessagePrimitive.Root className="...">
<div className="...">
<MessagePrimitive.GroupedParts // [!code ++]
groupBy={groupPartByType({ // [!code ++]
reasoning: ["group-reasoning"], // [!code ++]
})} // [!code ++]
> // [!code ++]
{({ part, children }) => { // [!code ++]
switch (part.type) { // [!code ++]
case "group-reasoning": { // [!code ++]
const running = part.status.type === "running"; // [!code ++]
return ( // [!code ++]
<ReasoningRoot streaming={running}> // [!code ++]
<ReasoningTrigger active={running} /> // [!code ++]
<ReasoningContent aria-busy={running}> // [!code ++]
<ReasoningText>{children}</ReasoningText> // [!code ++]
</ReasoningContent> // [!code ++]
</ReasoningRoot> // [!code ++]
); // [!code ++]
} // [!code ++]
case "text": // [!code ++]
return <MarkdownText />; // [!code ++]
case "reasoning": // [!code ++]
return <Reasoning {...part} />; // [!code ++]
case "tool-call": // [!code ++]
return part.toolUI ?? <ToolFallback {...part} />; // [!code ++]
default: // [!code ++]
return null; // [!code ++]
} // [!code ++]
}} // [!code ++]
</MessagePrimitive.GroupedParts> // [!code ++]
</div>
<AssistantActionBar />
<BranchPicker className="..." />
</MessagePrimitive.Root>
);
};
GroupedParts calls your render function for both group nodes and leaf parts. The case "group-reasoning" branch renders the collapsible shell and must render {children}; that is where the individual reasoning parts get placed. The case "reasoning" branch renders each individual reasoning part inside that shell and must not render children. Removing either case breaks rendering.
How It Works#
The component consists of two parts:
Reasoning: Renders individual reasoning message part content (with markdown support)- Composable group pieces (
ReasoningRoot,ReasoningTrigger,ReasoningContent,ReasoningText): Wrap grouped reasoning children in a collapsible container
Consecutive reasoning parts are grouped by MessagePrimitive.GroupedParts. Use the composable API below to control the grouped layout.
When using the composable API,
ReasoningTextis a plain container. Add<MarkdownText />for markdown rendering.
Variants#
Use the variant prop on ReasoningRoot to change the visual style:
<ReasoningRoot variant="outline">...</ReasoningRoot>
<ReasoningRoot variant="ghost">...</ReasoningRoot>
<ReasoningRoot variant="muted">...</ReasoningRoot>
| Variant | Description |
|---|---|
outline | Rounded border (default) |
ghost | No additional styling |
muted | Muted background |
Examples#
Variant Showcase#
Each variant renders in its closed and open state side by side. Select a trigger to toggle that disclosure.
Streaming Reasoning#
The response shows partial reasoning and stays active. Select Stop to cancel the response and return the composer to its idle state.
Completed with Duration#
Set duration in seconds on ReasoningTrigger to show the completed reasoning time in the trigger label. The disclosure starts closed because defaultOpen is false. Select the trigger to open or close the reasoning text.
Long Reasoning#
The defaultOpen prop opens the disclosure when the page loads. ReasoningText holds long reasoning in a max-h-64 scroll area. Scroll the text. Then collapse and expand the disclosure.
Legacy ReasoningGroup#
ReasoningGroup is kept for existing code that still uses the deprecated components.ReasoningGroup prop on MessagePrimitive.Parts. New code should use MessagePrimitive.GroupedParts and compose the root/trigger/content pieces directly.
import { ReasoningGroup } from "@/components/assistant-ui/reasoning";
const ReasoningGroupImpl: ReasoningGroupComponent = ({
children,
startIndex,
endIndex,
}) => {
const isReasoningStreaming = useAuiState((s) => {
if (s.message.status?.type !== "running") return false;
const lastIndex = s.message.parts.length - 1;
if (lastIndex < 0) return false;
const lastType = s.message.parts[lastIndex]?.type;
if (lastType !== "reasoning") return false;
return lastIndex >= startIndex && lastIndex <= endIndex;
});
return (
<ReasoningRoot streaming={isReasoningStreaming}>
<ReasoningTrigger active={isReasoningStreaming} />
<ReasoningContent aria-busy={isReasoningStreaming}>
<ReasoningText>{children}</ReasoningText>
</ReasoningContent>
</ReasoningRoot>
);
};
API Reference#
Composable API#
All sub-components are exported for custom layouts:
| Component | Description |
|---|---|
ReasoningRoot | Collapsible container with scroll lock |
ReasoningTrigger | Button with icon, label, and shimmer |
ReasoningContent | Animated collapsible content wrapper |
ReasoningText | Text wrapper with slide/fade animation |
ReasoningFade | Gradient fade overlay at bottom |
import {
ReasoningRoot,
ReasoningTrigger,
ReasoningContent,
ReasoningText,
ReasoningFade,
} from "@/components/assistant-ui/reasoning";
<ReasoningRoot variant="muted">
<ReasoningTrigger active={isStreaming} />
<ReasoningContent>
<ReasoningText>{children}</ReasoningText>
</ReasoningContent>
</ReasoningRoot>
Related Components#
- ToolGroup - Similar grouping pattern for tool calls
- PartGrouping - Advanced grouping options for message parts