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 are exported directly from @assistant-ui/react-ink (e.g. ThreadRoot, ComposerInput), unlike the web package which uses namespace imports.

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 {
  ThreadRoot,
  ThreadMessages,
  ThreadEmpty,
  ThreadIf,
} from "@assistant-ui/react-ink";

ThreadRoot

Container Box for the thread area.

<ThreadRoot>
  {children}
</ThreadRoot>
PropTypeDescription
...restBoxPropsStandard Ink Box props

ThreadMessages

Renders the message list with automatic runtime integration. Each message is wrapped in a scoped context so that useAuiState((s) => s.message) works inside renderMessage.

<ThreadMessages
  renderMessage={({ message, index }) => <MessageBubble message={message} />}
/>
PropTypeDescription
renderMessage(info: { message: ThreadMessage; index: number }) => ReactElementMessage renderer

ThreadEmpty

Renders children only when the thread has no messages.

<ThreadEmpty>
  <Text dimColor>Send a message to get started</Text>
</ThreadEmpty>

ThreadIf

Conditional rendering based on thread state.

<ThreadIf empty>
  <Text>No messages yet</Text>
</ThreadIf>

<ThreadIf running>
  <Text color="yellow">Generating...</Text>
</ThreadIf>
PropTypeDescription
emptybooleanRender when thread is empty
runningbooleanRender when thread is running

ThreadSuggestion

Renders a suggestion button. Uses Ink Box + Text.

<ThreadSuggestion
  prompt="What is the weather?"
  method="replace"
  autoSend
/>
PropTypeDescription
promptstringThe suggestion text
method"replace" | "append"How to insert the suggestion
autoSendbooleanAutomatically send after inserting

Composer

import {
  ComposerRoot,
  ComposerInput,
  ComposerSend,
  ComposerCancel,
  ComposerAddAttachment,
  ComposerAttachments,
  ComposerIf,
} from "@assistant-ui/react-ink";

ComposerRoot

Container Box for the composer area.

<ComposerRoot>
  {children}
</ComposerRoot>

ComposerInput

Ink TextInput wired to the composer runtime. Value is managed automatically.

<ComposerInput
  submitOnEnter
  placeholder="Type a message..."
  autoFocus
/>
PropTypeDescription
submitOnEnterbooleanWhether Enter sends the message (default: true)
placeholderstringPlaceholder text when empty
autoFocusbooleanAuto-focus on mount

ComposerSend

Box that triggers sending the current message. Typically used with a button-like UI.

<ComposerSend>
  <Text color="green">[Send]</Text>
</ComposerSend>

ComposerCancel

Box that cancels the current run.

<ComposerCancel>
  <Text color="red">[Stop]</Text>
</ComposerCancel>

ComposerAttachments

Renders composer attachments using the provided component configuration.

<ComposerAttachments
  components={{
    Attachment: MyAttachment,
  }}
/>
PropTypeDescription
components{ Image?, Document?, File?, Attachment? }Component renderers by attachment type

ComposerAddAttachment

Triggers attachment addition.

<ComposerAddAttachment>
  <Text>[Attach]</Text>
</ComposerAddAttachment>

ComposerIf

Conditional rendering based on composer state. Shared from @assistant-ui/core/react.

<ComposerIf editing>
  <Text>Currently editing</Text>
</ComposerIf>
PropTypeDescription
editingbooleanRender when composer is in editing mode

Message

import {
  MessageRoot,
  MessageContent,
  MessageIf,
  MessageParts,
  MessageAttachments,
} from "@assistant-ui/react-ink";

MessageRoot

Container Box for a single message.

<MessageRoot>
  {children}
</MessageRoot>

MessageContent

Renders message content parts. Tool call and data parts automatically render registered tool UIs (via useAssistantTool / useAssistantDataUI), falling back to render props if provided.

<MessageContent
  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

MessageIf

Conditional rendering based on message properties.

<MessageIf user>
  <Text bold color="green">You:</Text>
</MessageIf>

<MessageIf assistant last>
  <Text color="yellow">Thinking...</Text>
</MessageIf>
PropTypeDescription
userbooleanRender for user messages
assistantbooleanRender for assistant messages
runningbooleanRender when message is being generated
lastbooleanRender for the last message

MessageAttachments

Renders user message attachments using the provided component configuration.

<MessageAttachments
  components={{
    Attachment: ({ attachment }) => <Text>[{attachment.name}]</Text>,
  }}
/>
PropTypeDescription
components{ Image?, Document?, File?, Attachment? }Component renderers by attachment type

Attachment

import {
  AttachmentRoot,
  AttachmentName,
  AttachmentThumb,
  AttachmentRemove,
} from "@assistant-ui/react-ink";

Primitives for rendering individual attachments.

AttachmentRoot

Container Box for an attachment.

AttachmentName

Text component displaying the attachment filename.

AttachmentThumb

Text component displaying the file extension.

AttachmentRemove

Pressable that removes the attachment from the composer.

<AttachmentRoot>
  <AttachmentThumb />
  <AttachmentName />
  <AttachmentRemove>
    <Text color="red">[x]</Text>
  </AttachmentRemove>
</AttachmentRoot>

ActionBar

import {
  ActionBarCopy,
  ActionBarEdit,
  ActionBarReload,
  ActionBarFeedbackPositive,
  ActionBarFeedbackNegative,
} from "@assistant-ui/react-ink";

ActionBarCopy

Pressable that copies the message content. Supports function-as-children for copy state feedback.

<ActionBarCopy copiedDuration={3000}>
  {({ isCopied }) => <Text>{isCopied ? "[Copied!]" : "[Copy]"}</Text>}
</ActionBarCopy>
PropTypeDescription
copiedDurationnumberDuration in ms to show "copied" state (default: 3000)
copyToClipboard(text: string) => voidCustom clipboard function

ActionBarEdit

Pressable that enters edit mode for a message.

<ActionBarEdit>
  <Text>[Edit]</Text>
</ActionBarEdit>

ActionBarReload

Pressable that regenerates an assistant message.

<ActionBarReload>
  <Text>[Retry]</Text>
</ActionBarReload>

ActionBarFeedbackPositive / ActionBarFeedbackNegative

Pressable buttons for submitting message feedback.

<ActionBarFeedbackPositive>
  {({ isSubmitted }) => <Text>{isSubmitted ? "[Liked]" : "[Like]"}</Text>}
</ActionBarFeedbackPositive>

<ActionBarFeedbackNegative>
  {({ isSubmitted }) => <Text>{isSubmitted ? "[Disliked]" : "[Dislike]"}</Text>}
</ActionBarFeedbackNegative>

BranchPicker

import {
  BranchPickerPrevious,
  BranchPickerNext,
  BranchPickerNumber,
  BranchPickerCount,
} from "@assistant-ui/react-ink";

BranchPickerPrevious / BranchPickerNext

Pressable buttons to navigate between message branches.

<Box>
  <BranchPickerPrevious>
    <Text>{"<"}</Text>
  </BranchPickerPrevious>
  <BranchPickerNumber />
  <Text>/</Text>
  <BranchPickerCount />
  <BranchPickerNext>
    <Text>{">"}</Text>
  </BranchPickerNext>
</Box>

BranchPickerNumber / BranchPickerCount

Text components displaying the current branch number and total count.

ThreadList

import {
  ThreadListRoot,
  ThreadListItems,
  ThreadListNew,
} from "@assistant-ui/react-ink";

ThreadListRoot

Container Box for the thread list.

ThreadListItems

Renders thread list items with runtime integration.

<ThreadListItems
  renderItem={({ threadId }) => (
    <ThreadEntry threadId={threadId} />
  )}
/>
PropTypeDescription
renderItem(props: { threadId: string; index: number }) => ReactElementThread item renderer

ThreadListNew

Pressable that creates a new thread.

<ThreadListNew>
  <Text color="green">[New Chat]</Text>
</ThreadListNew>

ThreadListItem

import {
  ThreadListItemRoot,
  ThreadListItemTrigger,
  ThreadListItemDelete,
  ThreadListItemArchive,
  ThreadListItemUnarchive,
} from "@assistant-ui/react-ink";

ThreadListItemRoot

Container Box for a thread list item.

ThreadListItemTrigger

Pressable that switches to the thread.

ThreadListItemDelete

Pressable that deletes the thread.

ThreadListItemArchive / ThreadListItemUnarchive

Pressable buttons that archive or unarchive the thread.

<ThreadListItemRoot>
  <ThreadListItemTrigger>
    <Text>Chat #1</Text>
  </ThreadListItemTrigger>
  <ThreadListItemDelete>
    <Text color="red">[x]</Text>
  </ThreadListItemDelete>
</ThreadListItemRoot>

Suggestion

import {
  SuggestionTitle,
  SuggestionDescription,
  SuggestionTrigger,
} from "@assistant-ui/react-ink";

SuggestionTitle

Text component displaying the suggestion title.

SuggestionDescription

Text component displaying the suggestion description.

SuggestionTrigger

Pressable that triggers the suggestion action.

<SuggestionTrigger send>
  <SuggestionTitle />
</SuggestionTrigger>
PropTypeDescription
sendbooleanWhen true, sends immediately; when false, inserts into composer
clearComposerbooleanWhether to clear/replace composer text (default: true)

ChainOfThought

import {
  ChainOfThoughtRoot,
  ChainOfThoughtAccordionTrigger,
} from "@assistant-ui/react-ink";

ChainOfThoughtRoot

Container Box for chain of thought content.

ChainOfThoughtAccordionTrigger

Pressable that toggles the collapsed state of the chain of thought.

<ChainOfThoughtRoot>
  <ChainOfThoughtAccordionTrigger>
    <Text dimColor>[Toggle reasoning]</Text>
  </ChainOfThoughtAccordionTrigger>
  {/* reasoning content */}
</ChainOfThoughtRoot>

ToolFallback

import { ToolFallback } from "@assistant-ui/react-ink";

A built-in component for rendering tool calls with expandable/collapsible output. Includes spinner for running tools and formatted JSON output.

<MessageContent
  renderToolCall={({ part }) => <ToolFallback part={part} />}
/>