Primitives

Composable terminal components for building chat UIs with Ink.

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>
PropTypeDescription
...restBoxPropsStandard 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>
PropTypeDescription
componentsMessageComponentsComponent 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>
PropTypeDescription
condition(state) => booleanSelector 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>
PropTypeDescription
emptybooleanRender when thread is empty
runningbooleanRender 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>
PropTypeDescription
indexnumberZero-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>
PropTypeDescription
children() => ReactElementRender 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>
PropTypeDescription
indexnumberZero-based suggestion index

Suggestion

Renders a suggestion button. Uses Ink Box + Text.

<ThreadPrimitive.Suggestion
  prompt="What is the weather?"
  send
/>
PropTypeDescription
promptstringThe suggestion text
sendbooleanWhen true, automatically sends the message
clearComposerbooleanWhen 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
/>
PropTypeDescription
submitOnEnterbooleanWhether Enter sends the message (default: false)
placeholderstringPlaceholder text when empty
autoFocusbooleanAuto-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>
PropTypeDescription
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>
PropTypeDescription
indexnumberZero-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>
PropTypeDescription
editingbooleanRender when composer is in editing mode
dictationbooleanRender 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 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>}
/>
PropTypeDescription
renderText(props: { part; index }) => ReactElementText part renderer
renderToolCall(props: { part; index }) => ReactElementTool call fallback
renderImage(props: { part; index }) => ReactElementImage part renderer
renderReasoning(props: { part; index }) => ReactElementReasoning part renderer
renderSource(props: { part; index }) => ReactElementSource part renderer
renderFile(props: { part; index }) => ReactElementFile part renderer
renderData(props: { part; index }) => ReactElementData 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>
PropTypeDescription
indexnumberZero-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>
PropTypeDescription
indexnumberZero-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>
PropTypeDescription
userbooleanRender for user messages
assistantbooleanRender for assistant messages
runningbooleanRender when message is being generated
lastbooleanRender for the last message

Attachments

Renders user message attachments using the provided component configuration.

<MessagePrimitive.Attachments>
  {() => <Text>[attachment]</Text>}
</MessagePrimitive.Attachments>
PropTypeDescription
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>
PropTypeDescription
copiedDurationnumberDuration in ms to show "copied" state (default: 3000)
copyToClipboard(text: string) => voidCustom 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} />
  )}
/>
PropTypeDescription
renderItem(props: { threadId: string; index: number }) => ReactElementThread 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>
PropTypeDescription
sendbooleanWhen true, sends immediately; when false, inserts into composer
clearComposerbooleanWhether 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>
PropTypeDescription
partToolCallMessagePartPropsThe tool call message part (pass the component prop directly)
expandedbooleanForce expanded or collapsed. When unset, auto-expands while running, awaiting action, or errored
maxArgLinesnumberMaximum lines to show for args when expanded (default: 20)
maxResultLinesnumberMaximum lines to show for result when expanded (default: 20)
maxResultPreviewLinesnumberMaximum lines to show for result preview when collapsed (default: 1)
renderHeader(props: { toolName, status, expanded }) => ReactNodeCustom header renderer
renderArgs(props: { args, argsText }) => ReactNodeCustom args renderer
renderResult(props: { result, isError }) => ReactNodeCustom 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
/>
PropTypeDescription
patchstringUnified 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
showLineNumbersbooleanWhether to show line numbers (default: true)
contextLinesnumberNumber of context lines to show around changes
maxLinesnumberMaximum 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>
PropTypeDescription
patchstringUnified diff patch string
oldFile{ content: string; name?: string }Old file for inline diff
newFile{ content: string; name?: string }New file for inline diff

Displays file header information including filename(s) and change statistics.

<DiffPrimitive.Header fileIndex={0} />
PropTypeDescription
fileIndexnumberIndex 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} />}
/>
PropTypeDescription
fileIndexnumberIndex of file to display (default: 0)
showLineNumbersbooleanShow line numbers (default: true)
contextLinesnumberNumber of context lines around changes
maxLinesnumberMaximum lines to display
renderLine(props: { line; index }) => ReactNodeCustom line renderer
renderFold(props: { region; index }) => ReactNodeCustom fold region renderer

Line

Renders an individual diff line with appropriate coloring and indicators.

<DiffPrimitive.Line
  line={parsedLine}
  showLineNumbers
  lineNumberWidth={4}
/>
PropTypeDescription
lineParsedLineThe parsed line object to render
showLineNumbersbooleanShow line numbers (default: true)
lineNumberWidthnumberWidth for line number padding (default: 4)

Stats

Displays diff statistics (additions and deletions count).

<DiffPrimitive.Stats fileIndex={0} />
PropTypeDescription
fileIndexnumberIndex 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>