import { ThreadSample } from "@/components/pages/docs/samples/thread";
import { ThreadHistorySample } from "@/components/pages/docs/samples/thread/thread-history";
import { ThreadActionsSample } from "@/components/pages/docs/samples/thread/message-actions";
import { PreviewCode } from "@/components/pages/docs/preview-code.server";
import { ThreadWelcomeSuggestionsSample } from "@/components/pages/docs/samples/thread/welcome-with-suggestions";
import { ThreadBranchSample } from "@/components/pages/docs/samples/thread/branching-response";
import { ThreadRunningSample } from "@/components/pages/docs/samples/thread/running-response";
A complete chat interface that combines message rendering, auto-scrolling, composer input,
attachments, and conditional UI states. Fully customizable and composable.
Anatomy#
The Thread component is built with the following primitives:
import { ThreadPrimitive, AuiIf } from "@assistant-ui/react";
<ThreadPrimitive.Root>
<ThreadPrimitive.Viewport>
<AuiIf condition={(s) => s.thread.isEmpty}>
<ThreadWelcome />
{/* ThreadWelcome includes ThreadPrimitive.Suggestions */}
</AuiIf>
<ThreadPrimitive.Messages>
{({ message }) => {
if (message.role === "user") return <UserMessage />;
return <AssistantMessage />;
}}
</ThreadPrimitive.Messages>
<ThreadPrimitive.ViewportFooter>
<ThreadPrimitive.ScrollToBottom />
<Composer />
</ThreadPrimitive.ViewportFooter>
</ThreadPrimitive.Viewport>
</ThreadPrimitive.Root>
Getting Started#
Add the component#
<InstallCommand shadcn={["thread"]} />
This adds a /components/assistant-ui/thread.tsx file to your project, which you can adjust as needed.
Use in your application#
import { Thread } from "@/components/assistant-ui/thread";
export default function Chat() {
return (
<div className="h-full">
<Thread />
</div>
);
}
Examples#
Welcome Screen#
<AuiIf condition={(s) => s.thread.isEmpty}>
<ThreadWelcome />
</AuiIf>
Viewport Spacer#
<AuiIf condition={(s) => !s.thread.isEmpty}>
<div className="min-h-8 grow" />
</AuiIf>
Thread with History#
The thread shows a completed multi-turn conversation. The composer stays below the
messages, ready for the next input.
Conditional Send/Cancel Button#
<AuiIf condition={(s) => !s.thread.isRunning}>
<ComposerPrimitive.Send>
Send
</ComposerPrimitive.Send>
</AuiIf>
<AuiIf condition={(s) => s.thread.isRunning}>
<ComposerPrimitive.Cancel>
Cancel
</ComposerPrimitive.Cancel>
</AuiIf>
While the last assistant response runs, the composer shows the cancel action, not the send action, and the action bar of the response stays hidden. Click the stop button to cancel the run. The composer then shows the send action again.
Suggestions#
Display suggested prompts using the Suggestions API. See the Suggestions guide for detailed configuration.
An empty thread centers the welcome screen and shows the configured suggestion chips. When you select a chip, the thread sends its prompt.
<PreviewCode
file="components/pages/docs/samples/thread/welcome-with-suggestions"
name="ChatWithSuggestions"
import type { ReactNode } from "react";
import {
AssistantRuntimeProvider,
AuiConfig,
SuggestionPrimitive,
Suggestions,
ThreadPrimitive,
} from "@assistant-ui/react";
import { useChatRuntime } from "@assistant-ui/ai-sdk";
// Configure suggestions in your runtime provider
const config = AuiConfig({
suggestions: Suggestions(["What's the weather?", "Tell me a joke"]),
});
const App = ({ children }: { children: ReactNode }) => {
const runtime = useChatRuntime();
return (
<AssistantRuntimeProvider runtime={runtime} config={config}>
{children}
</AssistantRuntimeProvider>
);
};
// Display suggestions in your thread component
const ThreadSuggestions = () => (
<ThreadPrimitive.Suggestions>
{() => <SuggestionItem />}
</ThreadPrimitive.Suggestions>
);
// Custom suggestion item
const SuggestionItem = () => (
<SuggestionPrimitive.Trigger send asChild>
<button>
<SuggestionPrimitive.Title />
</button>
</SuggestionPrimitive.Trigger>
);
Branching Response#
Select the refresh action below the assistant response to make an alternate branch. The built-in branch picker then shows 2 / 2 with arrows that switch between the two branches.
<PreviewCode
file="components/pages/docs/samples/thread/branching-response"
name="Chat"
Message Actions#
Hover over the first assistant response to show its action bar. When the thread is idle, the last response always shows its actions. The copied Thread component sets hideWhenRunning and autohide="not-last" on ActionBarPrimitive.Root. Refer to the ActionBar primitive to change this behavior.
Component Overrides#
Thread accepts an optional components prop that swaps parts of the rendering without editing the copied file. All slots are optional; omitted slots keep the built-in rendering.
import { Thread, type ThreadComponents } from "@/components/assistant-ui/thread";
const THREAD_COMPONENTS: ThreadComponents = {
ToolFallback: MyToolFallback,
ToolGroup: MyToolGroup,
};
export default function Chat() {
return <Thread components={THREAD_COMPONENTS} />;
}
<ParametersTable
type="ThreadComponents"
parameters={[
{
name: "AssistantMessage",
type: "ComponentType",
description: "Replaces the entire assistant message, including the action bar and branch picker.",
},
{
name: "Welcome",
type: "ComponentType",
description: "Replaces the welcome screen shown for a new chat.",
},
{
name: "ToolFallback",
type: "ToolCallMessagePartComponent",
description: "Renders tool calls that have no tool UI registered by name. Registered tool UIs take precedence over this slot.",
},
{
name: "ToolGroup",
type: "ComponentType<PropsWithChildren<{ group: ThreadGroupPart }>>",
description: "Wraps runs of consecutive tool calls. Receives the group part (indices, status) and the rendered children.",
},
{
name: "ReasoningGroup",
type: "ComponentType<PropsWithChildren<{ group: ThreadGroupPart }>>",
description: "Wraps runs of consecutive reasoning parts. Receives the group part and the rendered children.",
},
]}
/>
Define the components object once at module scope (or memoize it) so message subtrees do not re-render whenever the parent re-renders.
For per-tool UI, prefer registering a renderer by tool name over overriding ToolFallback: put render on the matching toolkit entry (see Tool UI). data message parts render through renderers registered with useAssistantDataUI; parts without a registered renderer are not displayed.
API Reference#
The following primitives are used within the Thread component and can be customized in your /components/assistant-ui/thread.tsx file.
Root#
Contains all parts of the thread.
<ParametersTable
type="ThreadPrimitiveRootProps"
parameters={[
{
name: "asChild",
type: "boolean",
default: "false",
description: "Merge props with child element instead of rendering a wrapper div.",
},
{
name: "className",
type: "string",
description: "CSS class name.",
},
]}
/>
This primitive renders a <div> element unless asChild is set.
Viewport#
The scrollable area containing all messages. Automatically scrolls to the bottom as new messages are added.
<ParametersTable
type="ThreadPrimitiveViewportProps"
parameters={[
{
name: "asChild",
type: "boolean",
default: "false",
description: "Merge props with child element instead of rendering a wrapper div.",
},
{
name: "autoScroll",
type: "boolean",
default: 'true (false when turnAnchor is "top")',
description:
"Whether to automatically scroll to the bottom when new messages are added while the viewport was previously scrolled to the bottom.",
},
{
name: "turnAnchor",
type: '"top" | "bottom"',
default: '"bottom"',
description:
'Controls scroll anchoring behavior for new messages. "top" anchors new user messages at the top of the viewport.',
},
{
name: "topAnchorMessageClamp",
type: '{ tallerThan?: string; visibleHeight?: string }',
default: '{ tallerThan: "10em", visibleHeight: "6em" }',
description:
'Clamps tall user messages when turnAnchor is "top". Messages up to tallerThan stay fully visible; taller messages show only visibleHeight of their bottom edge above the assistant response.',
children: [
{
type: '{ tallerThan?: string; visibleHeight?: string }',
parameters: [
{
name: "tallerThan",
type: "string",
default: '"10em"',
description: "Clamp messages taller than this CSS length.",
},
{
name: "visibleHeight",
type: "string",
default: '"6em"',
description:
"Visible portion of a clamped message's bottom edge.",
},
],
},
],
},
{
name: "scrollToBottomOnRunStart",
type: "boolean",
default: "true",
description: "Whether to scroll to bottom when a new run starts.",
},
{
name: "scrollToBottomOnInitialize",
type: "boolean",
default: "true",
description:
"Whether to scroll to bottom when thread history is first loaded.",
},
{
name: "scrollToBottomOnThreadSwitch",
type: "boolean",
default: "true",
description:
"Whether to scroll to bottom when switching to a different thread.",
},
{
name: "className",
type: "string",
description: "CSS class name.",
},
]}
/>
This primitive renders a <div> element unless asChild is set.
Messages#
Renders all messages in the thread. This primitive renders a separate component for each message.
<ThreadPrimitive.Messages>
{({ message }) => {
if (message.role === "user") return <UserMessage />;
return <AssistantMessage />;
}}
</ThreadPrimitive.Messages>
<ParametersTable
type="ThreadPrimitiveMessagesProps"
parameters={[
{
name: "components",
type: "MessageComponents",
required: true,
description: "Components to render for different message types.",
children: [
{
type: "MessageComponents",
parameters: [
{
name: "Message",
type: "ComponentType",
description: "Default component for all messages.",
},
{
name: "UserMessage",
type: "ComponentType",
description: "Component for user messages.",
},
{
name: "EditComposer",
type: "ComponentType",
description:
"Component for user messages being edited.",
},
{
name: "AssistantMessage",
type: "ComponentType",
description: "Component for assistant messages.",
},
{
name: "SystemMessage",
type: "ComponentType",
description: "Component for system messages.",
},
],
},
],
},
]}
/>
MessageByIndex#
Renders a single message at the specified index.
<ThreadPrimitive.MessageByIndex
index={0}
components={{
UserMessage: UserMessage,
AssistantMessage: AssistantMessage
}}
/>
<ParametersTable
type="ThreadPrimitiveMessageByIndexProps"
parameters={[
{
name: "index",
type: "number",
required: true,
description: "The index of the message to render.",
},
{
name: "components",
type: "MessageComponents",
description: "Components to render for different message types.",
},
]}
/>
Empty#
Renders children only when there are no messages in the thread.
<ParametersTable
type="ThreadPrimitiveEmptyProps"
parameters={[
{
name: "children",
type: "ReactNode",
description: "Content to display when the thread is empty.",
},
]}
/>
ScrollToBottom#
A button to scroll the viewport to the bottom. Disabled when the viewport is already at the bottom.
<ParametersTable
type="ThreadPrimitiveScrollToBottomProps"
parameters={[
{
name: "asChild",
type: "boolean",
default: "false",
description: "Merge props with child element instead of rendering a wrapper button.",
},
{
name: "className",
type: "string",
description: "CSS class name.",
},
]}
/>
This primitive renders a <button> element unless asChild is set.
Suggestions#
Renders all configured suggestions. Configure suggestions using the Suggestions() API in your runtime provider.
<ThreadPrimitive.Suggestions>
{() => <CustomSuggestionComponent />}
</ThreadPrimitive.Suggestions>
<ParametersTable
type="ThreadPrimitiveSuggestionsProps"
parameters={[
{
name: "components",
type: "{ Suggestion: ComponentType }",
description: "Custom component to render each suggestion.",
},
]}
/>
AuiIf#
Conditionally renders children based on assistant state. This is a generic component that can access thread, message, composer, and other state.
import { AuiIf } from "@assistant-ui/react";
<AuiIf condition={(s) => s.thread.isEmpty}>
<WelcomeScreen />
</AuiIf>
<AuiIf condition={(s) => s.thread.isRunning}>
<LoadingIndicator />
</AuiIf>
<AuiIf condition={(s) => s.message.role === "assistant"}>
<AssistantAvatar />
</AuiIf>
<ParametersTable
type="AuiIfProps"
parameters={[
{
name: "condition",
type: "(state: AssistantState) => boolean",
required: true,
description: "A function that receives the assistant state and returns whether to render children.",
},
]}
/>
Related Components#
- ThreadList - List of threads, with or without sidebar
- Quoting guide - Quote selected text from messages
- SelectionToolbarPrimitive - Floating toolbar API reference