Primitives are thin wrappers around Ink components (Box, Text, TextInput) that integrate with the assistant-ui runtime. They accept all standard Ink props and add runtime-aware behavior.
Primitives use namespace imports, the same pattern as the web package (e.g. ThreadPrimitive.Root, ComposerPrimitive.Input).
Many primitives share their core logic with @assistant-ui/react via @assistant-ui/core/react — only the UI layer (Ink vs DOM) differs.
Thread#
import { ThreadPrimitive } from "@assistant-ui/react-ink";
Root#
Container Box for the thread area.
<ThreadPrimitive.Root>
{children}
</ThreadPrimitive.Root>
| Prop | Type | Description |
|---|---|---|
...rest | BoxProps | Standard Ink Box props |
Messages#
Renders the message list with automatic runtime integration. Each message is wrapped in a scoped context so that useAuiState((s) => s.message) works inside the message component.
<ThreadPrimitive.Messages>
{() => <MyMessage />}
</ThreadPrimitive.Messages>
| Prop | Type | Description |
|---|---|---|
components | MessageComponents | Component map with Message, UserMessage, AssistantMessage, SystemMessage, and edit composer variants |
AuiIf#
Conditional rendering based on assistant state. Replaces the deprecated ThreadPrimitive.Empty, ThreadPrimitive.If, MessagePrimitive.If, and ComposerPrimitive.If.
import { AuiIf } from "@assistant-ui/react-ink";
<AuiIf condition={(s) => s.thread.isEmpty}>
<Text dimColor>Send a message to get started</Text>
</AuiIf>
<AuiIf condition={(s) => s.thread.isRunning}>
<Text color="yellow">Generating...</Text>
</AuiIf>
| Prop | Type | Description |
|---|---|---|
condition | (state) => boolean | Selector that determines whether children are rendered |
Empty (deprecated)#
Deprecated. Use `AuiIf` with `condition={(s) => s.thread.isEmpty}` instead.Renders children only when the thread has no messages.
<ThreadPrimitive.Empty>
<Text dimColor>Send a message to get started</Text>
</ThreadPrimitive.Empty>
If (deprecated)#
Deprecated. Use `AuiIf` instead.Conditional rendering based on thread state.
<ThreadPrimitive.If empty>
<Text>No messages yet</Text>
</ThreadPrimitive.If>
<ThreadPrimitive.If running>
<Text color="yellow">Generating...</Text>
</ThreadPrimitive.If>
| Prop | Type | Description |
|---|---|---|
empty | boolean | Render when thread is empty |
running | boolean | Render when thread is running |
MessageByIndex#
Low-level provider that sets up the message context for a specific message by index. Used internally by ThreadPrimitive.Messages.
<ThreadPrimitive.MessageByIndex index={0}>
{/* content rendered with message context */}
</ThreadPrimitive.MessageByIndex>
| Prop | Type | Description |
|---|---|---|
index | number | Zero-based message index |
Suggestions#
Renders all thread suggestions using a provided component. Each suggestion is wrapped in a SuggestionByIndexProvider context.
<ThreadPrimitive.Suggestions>
{() => <MySuggestion />}
</ThreadPrimitive.Suggestions>
| Prop | Type | Description |
|---|---|---|
children | () => ReactElement | Render function for each suggestion |
SuggestionByIndex#
Low-level provider that sets up the suggestion context for a specific suggestion by index. Used internally by ThreadPrimitive.Suggestions.
<ThreadPrimitive.SuggestionByIndex index={0}>
<SuggestionPrimitive.Title />
</ThreadPrimitive.SuggestionByIndex>
| Prop | Type | Description |
|---|---|---|
index | number | Zero-based suggestion index |
Suggestion#
Renders a suggestion button. Uses Ink Box + Text.
<ThreadPrimitive.Suggestion
prompt="What is the weather?"
send
/>
| Prop | Type | Description |
|---|---|---|
prompt | string | The suggestion text |
send | boolean | When true, automatically sends the message |
clearComposer | boolean | When true (default), replaces composer text; when false, appends |
Composer#
import { ComposerPrimitive } from "@assistant-ui/react-ink";
Root#
Container Box for the composer area.
<ComposerPrimitive.Root>
{children}
</ComposerPrimitive.Root>
Input#
Ink TextInput wired to the composer runtime. Value is managed automatically.
<ComposerPrimitive.Input
submitOnEnter
placeholder="Type a message..."
autoFocus
/>
| Prop | Type | Description |
|---|---|---|
submitOnEnter | boolean | Whether Enter sends the message (default: false) |
placeholder | string | Placeholder text when empty |
autoFocus | boolean | Auto-focus on mount |
Send#
Box that triggers sending the current message. Typically used with a button-like UI.
<ComposerPrimitive.Send>
<Text color="green">[Send]</Text>
</ComposerPrimitive.Send>
Cancel#
Box that cancels the current run.
<ComposerPrimitive.Cancel>
<Text color="red">[Stop]</Text>
</ComposerPrimitive.Cancel>
Attachments#
Renders composer attachments using the provided component configuration.
<ComposerPrimitive.Attachments>
{() => <MyAttachment />}
</ComposerPrimitive.Attachments>
| Prop | Type | Description |
|---|---|---|
components | { Image?, Document?, File?, Attachment? } | Component renderers by attachment type |
AddAttachment#
Triggers attachment addition.
<ComposerPrimitive.AddAttachment>
<Text>[Attach]</Text>
</ComposerPrimitive.AddAttachment>
Conditional Rendering (Composer)#
Use AuiIf for conditional rendering based on composer state:
<AuiIf condition={(s) => s.composer.isEditing}>
<Text>Currently editing</Text>
</AuiIf>
AttachmentByIndex#
Low-level provider that sets up the attachment context for a specific attachment by index. Used internally by ComposerPrimitive.Attachments.
<ComposerPrimitive.AttachmentByIndex index={0}>
<AttachmentPrimitive.Name />
</ComposerPrimitive.AttachmentByIndex>
| Prop | Type | Description |
|---|---|---|
index | number | Zero-based attachment index |
If (deprecated)#
Deprecated. Use `AuiIf` instead.Conditional rendering based on composer state. Shared from @assistant-ui/core/react.
<ComposerPrimitive.If editing>
<Text>Currently editing</Text>
</ComposerPrimitive.If>
<ComposerPrimitive.If dictation>
<Text>Dictating...</Text>
</ComposerPrimitive.If>
| Prop | Type | Description |
|---|---|---|
editing | boolean | Render when composer is in editing mode |
dictation | boolean | Render when dictation is currently active |
Message#
import { MessagePrimitive } from "@assistant-ui/react-ink";
Root#
Container Box for a single message.
<MessagePrimitive.Root>
{children}
</MessagePrimitive.Root>
Parts#
Renders message content parts via a components prop. Tool call and data parts automatically render registered tool UIs (via useAssistantTool / useAssistantDataUI), falling back to components provided here. A default Text component using Ink's <Text> is provided out of the box.
<MessagePrimitive.Parts>
{({ part }) => {
if (part.type === "text") return <Text>{part.text}</Text>;
if (part.type === "tool-call") return <Text dimColor>[Tool: {part.toolName}]</Text>;
return null;
}}
</MessagePrimitive.Parts>
Content (deprecated)#
Deprecated. Use `MessagePrimitive.Parts` instead. See the [v0.11 migration guide](/docs/reference/migrations/v0-11) for details.Renders message content parts using render props. Tool call and data parts automatically render registered tool UIs (via useAssistantTool / useAssistantDataUI), falling back to render props if provided.
<MessagePrimitive.Content
renderText={({ part }) => <Text>{part.text}</Text>}
renderToolCall={({ part }) => <Text dimColor>[Tool: {part.toolName}]</Text>}
/>
| Prop | Type | Description |
|---|---|---|
renderText | (props: { part; index }) => ReactElement | Text part renderer |
renderToolCall | (props: { part; index }) => ReactElement | Tool call fallback |
renderImage | (props: { part; index }) => ReactElement | Image part renderer |
renderReasoning | (props: { part; index }) => ReactElement | Reasoning part renderer |
renderSource | (props: { part; index }) => ReactElement | Source part renderer |
renderFile | (props: { part; index }) => ReactElement | File part renderer |
renderData | (props: { part; index }) => ReactElement | Data part fallback |
PartByIndex#
Low-level provider that sets up the part context for a specific message part by index. Used internally by MessagePrimitive.Parts.
<MessagePrimitive.PartByIndex index={0}>
{/* content rendered with part context */}
</MessagePrimitive.PartByIndex>
| Prop | Type | Description |
|---|---|---|
index | number | Zero-based part index |
AttachmentByIndex#
Low-level provider that sets up the attachment context for a specific message attachment by index. Used internally by MessagePrimitive.Attachments.
<MessagePrimitive.AttachmentByIndex index={0}>
<AttachmentPrimitive.Name />
</MessagePrimitive.AttachmentByIndex>
| Prop | Type | Description |
|---|---|---|
index | number | Zero-based attachment index |
Conditional Rendering (Message)#
Use AuiIf for conditional rendering based on message properties:
<AuiIf condition={(s) => s.message.role === "user"}>
<Text bold color="green">You:</Text>
</AuiIf>
<AuiIf condition={(s) => s.message.role === "assistant" && s.message.isLast}>
<Text color="yellow">Thinking...</Text>
</AuiIf>
If (deprecated)#
Deprecated. Use `AuiIf` instead.Conditional rendering based on message properties.
<MessagePrimitive.If user>
<Text bold color="green">You:</Text>
</MessagePrimitive.If>
<MessagePrimitive.If assistant last>
<Text color="yellow">Thinking...</Text>
</MessagePrimitive.If>
| Prop | Type | Description |
|---|---|---|
user | boolean | Render for user messages |
assistant | boolean | Render for assistant messages |
running | boolean | Render when message is being generated |
last | boolean | Render for the last message |
Attachments#
Renders user message attachments using the provided component configuration.
<MessagePrimitive.Attachments>
{() => <Text>[attachment]</Text>}
</MessagePrimitive.Attachments>
| Prop | Type | Description |
|---|---|---|
components | { Image?, Document?, File?, Attachment? } | Component renderers by attachment type |
Attachment#
import { AttachmentPrimitive } from "@assistant-ui/react-ink";
Primitives for rendering individual attachments.
Root#
Container Box for an attachment.
Name#
Text component displaying the attachment filename.
Thumb#
Text component displaying the file extension.
Remove#
Pressable that removes the attachment from the composer.
<AttachmentPrimitive.Root>
<AttachmentPrimitive.Thumb />
<AttachmentPrimitive.Name />
<AttachmentPrimitive.Remove>
<Text color="red">[x]</Text>
</AttachmentPrimitive.Remove>
</AttachmentPrimitive.Root>
ActionBar#
import { ActionBarPrimitive } from "@assistant-ui/react-ink";
Copy#
Pressable that copies the message content. Supports function-as-children for copy state feedback.
<ActionBarPrimitive.Copy copiedDuration={3000}>
{({ isCopied }) => <Text>{isCopied ? "[Copied!]" : "[Copy]"}</Text>}
</ActionBarPrimitive.Copy>
| Prop | Type | Description |
|---|---|---|
copiedDuration | number | Duration in ms to show "copied" state (default: 3000) |
copyToClipboard | (text: string) => void | Custom clipboard function |
Edit#
Pressable that enters edit mode for a message.
<ActionBarPrimitive.Edit>
<Text>[Edit]</Text>
</ActionBarPrimitive.Edit>
Reload#
Pressable that regenerates an assistant message.
<ActionBarPrimitive.Reload>
<Text>[Retry]</Text>
</ActionBarPrimitive.Reload>
FeedbackPositive / FeedbackNegative#
Pressable buttons for submitting message feedback.
<ActionBarPrimitive.FeedbackPositive>
{({ isSubmitted }) => <Text>{isSubmitted ? "[Liked]" : "[Like]"}</Text>}
</ActionBarPrimitive.FeedbackPositive>
<ActionBarPrimitive.FeedbackNegative>
{({ isSubmitted }) => <Text>{isSubmitted ? "[Disliked]" : "[Dislike]"}</Text>}
</ActionBarPrimitive.FeedbackNegative>
BranchPicker#
import { BranchPickerPrimitive } from "@assistant-ui/react-ink";
Previous / Next#
Pressable buttons to navigate between message branches.
<Box>
<BranchPickerPrimitive.Previous>
<Text>{"<"}</Text>
</BranchPickerPrimitive.Previous>
<BranchPickerPrimitive.Number />
<Text>/</Text>
<BranchPickerPrimitive.Count />
<BranchPickerPrimitive.Next>
<Text>{">"}</Text>
</BranchPickerPrimitive.Next>
</Box>
Number / Count#
Text components displaying the current branch number and total count.
ThreadList#
import { ThreadListPrimitive } from "@assistant-ui/react-ink";
Root#
Container Box for the thread list.
Items#
Renders thread list items with runtime integration.
<ThreadListPrimitive.Items
renderItem={({ threadId }) => (
<ThreadEntry threadId={threadId} />
)}
/>
| Prop | Type | Description |
|---|---|---|
renderItem | (props: { threadId: string; index: number }) => ReactElement | Thread item renderer |
New#
Pressable that creates a new thread.
<ThreadListPrimitive.New>
<Text color="green">[New Chat]</Text>
</ThreadListPrimitive.New>
ThreadListItem#
import { ThreadListItemPrimitive } from "@assistant-ui/react-ink";
Root#
Container Box for a thread list item.
Title#
Text component displaying the thread list item title. Shared from @assistant-ui/core/react.
Trigger#
Pressable that switches to the thread.
Delete#
Pressable that deletes the thread.
Archive / Unarchive#
Pressable buttons that archive or unarchive the thread.
<ThreadListItemPrimitive.Root>
<ThreadListItemPrimitive.Trigger>
<ThreadListItemPrimitive.Title />
</ThreadListItemPrimitive.Trigger>
<ThreadListItemPrimitive.Delete>
<Text color="red">[x]</Text>
</ThreadListItemPrimitive.Delete>
</ThreadListItemPrimitive.Root>
Suggestion#
import { SuggestionPrimitive } from "@assistant-ui/react-ink";
Title#
Text component displaying the suggestion title.
Description#
Text component displaying the suggestion label. Despite the component name, this reads from the label field of the suggestion state (not a separate description field).
Trigger#
Pressable that triggers the suggestion action.
<SuggestionPrimitive.Trigger send>
<SuggestionPrimitive.Title />
</SuggestionPrimitive.Trigger>
| Prop | Type | Description |
|---|---|---|
send | boolean | When true, sends immediately; when false, inserts into composer |
clearComposer | boolean | Whether to clear/replace composer text (default: true) |
ChainOfThought#
import { ChainOfThoughtPrimitive } from "@assistant-ui/react-ink";
Root#
Container Box for chain of thought content.
AccordionTrigger#
Pressable that toggles the collapsed state of the chain of thought.
Parts#
Renders the individual parts of a chain of thought using a provided components map. Shared from @assistant-ui/core/react.
<ChainOfThoughtPrimitive.Root>
<ChainOfThoughtPrimitive.AccordionTrigger>
<Text dimColor>[Toggle reasoning]</Text>
</ChainOfThoughtPrimitive.AccordionTrigger>
<ChainOfThoughtPrimitive.Parts>
{({ part }) => part.type === "text" ? <Text dimColor>{part.text}</Text> : null}
</ChainOfThoughtPrimitive.Parts>
</ChainOfThoughtPrimitive.Root>
ToolCall#
import { ToolCallPrimitive } from "@assistant-ui/react-ink";
Fallback#
A built-in component for rendering tool calls with expandable/collapsible output. Includes a spinner for running tools, formatted JSON args and result output, and status icons for completed, errored, or interrupted calls.
<MessagePrimitive.Parts>
{({ part }) => {
if (part.type === "tool-call") return <ToolCallPrimitive.Fallback part={part} />;
return null;
}}
</MessagePrimitive.Parts>
| Prop | Type | Description |
|---|---|---|
part | ToolCallMessagePartProps | The tool call message part (pass the component prop directly) |
expanded | boolean | Force expanded or collapsed. When unset, auto-expands while running, awaiting action, or errored |
maxArgLines | number | Maximum lines to show for args when expanded (default: 20) |
maxResultLines | number | Maximum lines to show for result when expanded (default: 20) |
maxResultPreviewLines | number | Maximum lines to show for result preview when collapsed (default: 1) |
renderHeader | (props: { toolName, status, expanded }) => ReactNode | Custom header renderer |
renderArgs | (props: { args, argsText }) => ReactNode | Custom args renderer |
renderResult | (props: { result, isError }) => ReactNode | Custom result renderer |
Diff#
import { DiffPrimitive, DiffView } from "@assistant-ui/react-ink";
Components for rendering code diffs in the terminal.
DiffView#
Pre-composed component that combines all diff primitives for easy diff rendering. Accepts either a unified diff patch string or old/new file content for inline comparison.
// From a patch string
<DiffView
patch={unifiedDiffString}
showLineNumbers
contextLines={3}
maxLines={50}
/>
// From file contents
<DiffView
oldFile={{ content: "old text", name: "file.txt" }}
newFile={{ content: "new text", name: "file.txt" }}
showLineNumbers
/>
| Prop | Type | Description |
|---|---|---|
patch | string | Unified diff patch string to parse and display |
oldFile | { content: string; name?: string } | Old file content for inline comparison |
newFile | { content: string; name?: string } | New file content for inline comparison |
showLineNumbers | boolean | Whether to show line numbers (default: true) |
contextLines | number | Number of context lines to show around changes |
maxLines | number | Maximum number of lines to display |
Root#
Container Box that provides diff context to child components. Parses the patch or computes the diff from old/new file content.
<DiffPrimitive.Root
patch={patchString}
oldFile={{ content: oldContent, name: "file.txt" }}
newFile={{ content: newContent, name: "file.txt" }}
{children}
</DiffPrimitive.Root>
| Prop | Type | Description |
|---|---|---|
patch | string | Unified diff patch string |
oldFile | { content: string; name?: string } | Old file for inline diff |
newFile | { content: string; name?: string } | New file for inline diff |
Header#
Displays file header information including filename(s) and change statistics.
<DiffPrimitive.Header fileIndex={0} />
| Prop | Type | Description |
|---|---|---|
fileIndex | number | Index of file to display header for (default: 0) |
Content#
Renders the diff content with lines. Supports context folding and line truncation.
<DiffPrimitive.Content
fileIndex={0}
showLineNumbers
contextLines={3}
maxLines={100}
renderLine={({ line, index }) => <CustomLine line={line} />}
renderFold={({ region, index }) => <CustomFold region={region} />}
/>
| Prop | Type | Description |
|---|---|---|
fileIndex | number | Index of file to display (default: 0) |
showLineNumbers | boolean | Show line numbers (default: true) |
contextLines | number | Number of context lines around changes |
maxLines | number | Maximum lines to display |
renderLine | (props: { line; index }) => ReactNode | Custom line renderer |
renderFold | (props: { region; index }) => ReactNode | Custom fold region renderer |
Line#
Renders an individual diff line with appropriate coloring and indicators.
<DiffPrimitive.Line
line={parsedLine}
showLineNumbers
lineNumberWidth={4}
/>
| Prop | Type | Description |
|---|---|---|
line | ParsedLine | The parsed line object to render |
showLineNumbers | boolean | Show line numbers (default: true) |
lineNumberWidth | number | Width for line number padding (default: 4) |
Stats#
Displays diff statistics (additions and deletions count).
<DiffPrimitive.Stats fileIndex={0} />
| Prop | Type | Description |
|---|---|---|
fileIndex | number | Index of file to display stats for (default: 0) |
Composing with Primitives#
For custom layouts, use the primitives directly:
<DiffPrimitive.Root patch={patchString}>
<Box flexDirection="column">
<DiffPrimitive.Header />
<DiffPrimitive.Stats />
<DiffPrimitive.Content
showLineNumbers
contextLines={2}
/>
</Box>
</DiffPrimitive.Root>