assistant-ui Primitives
Always consult assistant-ui.com/llms.txt for the latest API.
Composable, unstyled components following Radix UI patterns.
References
Import
import {
AuiIf,
ThreadPrimitive,
ComposerPrimitive,
MessagePrimitive,
ActionBarPrimitive,
BranchPickerPrimitive,
AttachmentPrimitive,
ThreadListPrimitive,
ThreadListItemPrimitive,
} from "@assistant-ui/react";
Primitive Parts
| Primitive |
Key Parts |
ThreadPrimitive |
.Root, .Viewport, .Messages, .Empty, .ScrollToBottom |
ComposerPrimitive |
.Root, .Input, .Send, .Cancel, .Attachments |
MessagePrimitive |
.Root, .Parts/.Content, .If, .Error |
ActionBarPrimitive |
.Copy, .Edit, .Reload, .Speak, .FeedbackPositive, .FeedbackNegative, .ExportMarkdown |
BranchPickerPrimitive |
.Previous, .Next, .Number, .Count |
Custom Thread Example
function CustomThread() {
return (
<ThreadPrimitive.Root className="flex flex-col h-full">
<ThreadPrimitive.Empty>
<div className="flex-1 flex items-center justify-center">
Start a conversation
</div>
</ThreadPrimitive.Empty>
<ThreadPrimitive.Viewport className="flex-1 overflow-y-auto p-4">
<ThreadPrimitive.Messages>
{({ message }) =>
message.role === "user" ? (
<CustomUserMessage />
) : (
<CustomAssistantMessage />
)
}
</ThreadPrimitive.Messages>
</ThreadPrimitive.Viewport>
<ComposerPrimitive.Root className="border-t p-4 flex gap-2">
<ComposerPrimitive.Input className="flex-1 rounded-lg border px-4 py-2" />
<ComposerPrimitive.Send className="bg-blue-500 text-white px-4 py-2 rounded-lg">
Send
</ComposerPrimitive.Send>
</ComposerPrimitive.Root>
</ThreadPrimitive.Root>
);
}
Conditional Rendering
Prefer AuiIf for new code. Primitive .If components still exist but are deprecated.
<AuiIf condition={({ message }) => message.role === "user"}>
User only
</AuiIf>
<AuiIf condition={({ thread }) => thread.isRunning}>
Generating...
</AuiIf>
<AuiIf condition={({ message }) => message.branchCount > 1}>
Has edit history
</AuiIf>
<AuiIf condition={({ thread }) => thread.isRunning}>
<ComposerPrimitive.Cancel>Stop</ComposerPrimitive.Cancel>
</AuiIf>
<AuiIf condition={({ thread }) => thread.isEmpty}>No messages</AuiIf>
Message Parts (children render function)
As of 0.14, primitives that render lists (ThreadPrimitive.Messages, MessagePrimitive.Parts, ThreadPrimitive.Suggestions, ThreadListPrimitive.Items, ComposerPrimitive.Attachments) take a children render function instead of a components prop. The components prop still works but is deprecated.
MessagePrimitive.Parts is the canonical name (MessagePrimitive.Content is a deprecated alias).
<MessagePrimitive.Parts>
{({ part }) => {
switch (part.type) {
case "text":
return <p>{part.text}</p>;
case "image":
return <img src={part.image} alt="" />;
case "reasoning":
return (
<details>
<summary>Thinking</summary>
{part.text}
</details>
);
case "tool-call":
return part.toolUI ?? <div>Tool: {part.toolName}</div>;
default:
return null; // registered tool/data UIs still render
}
}}
</MessagePrimitive.Parts>
Returning null from the render function lets registered tool and data UIs render via the registry; return <></> to explicitly render nothing.
Branch Picker
<AuiIf condition={({ message }) => message.branchCount > 1}>
<BranchPickerPrimitive.Root className="flex items-center gap-1">
<BranchPickerPrimitive.Previous>←</BranchPickerPrimitive.Previous>
<span><BranchPickerPrimitive.Number /> / <BranchPickerPrimitive.Count /></span>
<BranchPickerPrimitive.Next>→</BranchPickerPrimitive.Next>
</BranchPickerPrimitive.Root>
</AuiIf>
Common Gotchas
Primitives not rendering
- Wrap in
AssistantRuntimeProvider
- Ensure parent primitive provides context
Styles not applying
- Primitives are unstyled by default
- Add
className and style with your app's Tailwind/CSS system
1---2name: primitives3description: Builds and customizes assistant-ui chat UI from composable, unstyled @assistant-ui/react primitives that follow Radix-style part composition. Use when assembling or styling a custom Thread, Composer, message rendering, action bar, or branch picker from building blocks: ThreadPrimitive (.Root, .Viewport, .Messages, .Empty, .ScrollToBottom), ComposerPrimitive (.Input, .Send, .Cancel, .Attachments), MessagePrimitive (.Parts/.Content, .Error), ActionBarPrimitive (.Copy, .Edit, .Reload, .Speak, feedback, .ExportMarkdown), BranchPickerPrimitive, AttachmentPrimitive, ThreadListPrimitive, ThreadListItemPrimitive. Covers MessagePrimitive.Parts children render functions for text, image, reasoning, and tool-call parts; conditional rendering with AuiIf (deprecated .If); and gotchas like wrapping in AssistantRuntimeProvider and adding className since primitives ship unstyled. For prebuilt drop-in UI and scaffolding use setup; for multi-thread sidebar behavior use thread-list.4license: MIT5---67# assistant-ui Primitives89**Always consult [assistant-ui.com/llms.txt](https://www.assistant-ui.com/llms.txt) for the latest API.**1011Composable, unstyled components following Radix UI patterns.1213## References1415- [./references/thread.md](./references/thread.md) -- ThreadPrimitive deep dive16- [./references/composer.md](./references/composer.md) -- ComposerPrimitive deep dive17- [./references/message.md](./references/message.md) -- MessagePrimitive deep dive18- [./references/action-bar.md](./references/action-bar.md) -- ActionBarPrimitive deep dive19- [./references/part-grouping.md](./references/part-grouping.md) -- Part grouping and chain-of-thought UI20- [./references/mentions.md](./references/mentions.md) -- Composer mentions and slash commands2122## Import2324```tsx25import {26 AuiIf,27 ThreadPrimitive,28 ComposerPrimitive,29 MessagePrimitive,30 ActionBarPrimitive,31 BranchPickerPrimitive,32 AttachmentPrimitive,33 ThreadListPrimitive,34 ThreadListItemPrimitive,35} from "@assistant-ui/react";36```3738## Primitive Parts3940| Primitive | Key Parts |41|-----------|-----------|42| `ThreadPrimitive` | `.Root`, `.Viewport`, `.Messages`, `.Empty`, `.ScrollToBottom` |43| `ComposerPrimitive` | `.Root`, `.Input`, `.Send`, `.Cancel`, `.Attachments` |44| `MessagePrimitive` | `.Root`, `.Parts`/`.Content`, `.If`, `.Error` |45| `ActionBarPrimitive` | `.Copy`, `.Edit`, `.Reload`, `.Speak`, `.FeedbackPositive`, `.FeedbackNegative`, `.ExportMarkdown` |46| `BranchPickerPrimitive` | `.Previous`, `.Next`, `.Number`, `.Count` |4748## Custom Thread Example4950```tsx51function CustomThread() {52 return (53 <ThreadPrimitive.Root className="flex flex-col h-full">54 <ThreadPrimitive.Empty>55 <div className="flex-1 flex items-center justify-center">56 Start a conversation57 </div>58 </ThreadPrimitive.Empty>5960 <ThreadPrimitive.Viewport className="flex-1 overflow-y-auto p-4">61 <ThreadPrimitive.Messages>62 {({ message }) =>63 message.role === "user" ? (64 <CustomUserMessage />65 ) : (66 <CustomAssistantMessage />67 )68 }69 </ThreadPrimitive.Messages>70 </ThreadPrimitive.Viewport>7172 <ComposerPrimitive.Root className="border-t p-4 flex gap-2">73 <ComposerPrimitive.Input className="flex-1 rounded-lg border px-4 py-2" />74 <ComposerPrimitive.Send className="bg-blue-500 text-white px-4 py-2 rounded-lg">75 Send76 </ComposerPrimitive.Send>77 </ComposerPrimitive.Root>78 </ThreadPrimitive.Root>79 );80}81```8283## Conditional Rendering8485Prefer `AuiIf` for new code. Primitive `.If` components still exist but are deprecated.8687```tsx88<AuiIf condition={({ message }) => message.role === "user"}>89 User only90</AuiIf>91<AuiIf condition={({ thread }) => thread.isRunning}>92 Generating...93</AuiIf>94<AuiIf condition={({ message }) => message.branchCount > 1}>95 Has edit history96</AuiIf>9798<AuiIf condition={({ thread }) => thread.isRunning}>99 <ComposerPrimitive.Cancel>Stop</ComposerPrimitive.Cancel>100</AuiIf>101102<AuiIf condition={({ thread }) => thread.isEmpty}>No messages</AuiIf>103```104105## Message Parts (children render function)106107As of 0.14, primitives that render lists (`ThreadPrimitive.Messages`, `MessagePrimitive.Parts`, `ThreadPrimitive.Suggestions`, `ThreadListPrimitive.Items`, `ComposerPrimitive.Attachments`) take a children render function instead of a `components` prop. The `components` prop still works but is deprecated.108109`MessagePrimitive.Parts` is the canonical name (`MessagePrimitive.Content` is a deprecated alias).110111```tsx112<MessagePrimitive.Parts>113 {({ part }) => {114 switch (part.type) {115 case "text":116 return <p>{part.text}</p>;117 case "image":118 return <img src={part.image} alt="" />;119 case "reasoning":120 return (121 <details>122 <summary>Thinking</summary>123 {part.text}124 </details>125 );126 case "tool-call":127 return part.toolUI ?? <div>Tool: {part.toolName}</div>;128 default:129 return null; // registered tool/data UIs still render130 }131 }}132</MessagePrimitive.Parts>133```134135Returning `null` from the render function lets registered tool and data UIs render via the registry; return `<></>` to explicitly render nothing.136137## Branch Picker138139```tsx140<AuiIf condition={({ message }) => message.branchCount > 1}>141 <BranchPickerPrimitive.Root className="flex items-center gap-1">142 <BranchPickerPrimitive.Previous>←</BranchPickerPrimitive.Previous>143 <span><BranchPickerPrimitive.Number /> / <BranchPickerPrimitive.Count /></span>144 <BranchPickerPrimitive.Next>→</BranchPickerPrimitive.Next>145 </BranchPickerPrimitive.Root>146</AuiIf>147```148149## Common Gotchas150151**Primitives not rendering**152- Wrap in `AssistantRuntimeProvider`153- Ensure parent primitive provides context154155**Styles not applying**156- Primitives are unstyled by default157- Add `className` and style with your app's Tailwind/CSS system