Dosu LogoDosu Logo
Ask
Join our Discord
assistant-ui's SpacePublic
assistant-ui
Documentsassistant-ui's Space
primitives
primitives
Type
External
Status
Published
Created
Mar 17, 2026
Updated
Oct 6, 2026
Updated by
Dosu Bot
Source
apps/docs/content/docs/ink/primitives.mdx

Primitives are thin wrappers around Ink components (Box, Text) 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 instead of 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
children(value: { message: ThreadMessage }) => ReactNodeRender function for each message. Hoist or memoize it so per-message memoization applies
componentsMessageComponentsAlternative to children. Component map with Message, UserMessage, AssistantMessage, SystemMessage, and edit composer variants
windowSizenumberNumber of recent messages kept in the live render region. Older messages move into terminal scrollback through Ink's <Static> and stop repainting. When unset, all messages stay live
windowOverscannumberExtra live messages kept above the window (default: 4)

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#

Renders the message at a specific index and sets up its message context. Shared from @assistant-ui/core/react.

<ThreadPrimitive.MessageByIndex
  index={0}
  components={{ Message: MyMessage }}
/>
PropTypeDescription
indexnumberZero-based message index
componentsMessageComponentsSame component map as ThreadPrimitive.Messages

Suggestions#

Renders all thread suggestions through a children render function. Each suggestion is wrapped in a SuggestionByIndexProvider context.

<ThreadPrimitive.Suggestions>
  {() => <MySuggestion />}
</ThreadPrimitive.Suggestions>
PropTypeDescription
children(value: { suggestion: SuggestionState }) => ReactNodeRender function for each suggestion

SuggestionByIndex#

Renders the suggestion at a specific index and sets up its suggestion context.

<ThreadPrimitive.SuggestionByIndex
  index={0}
  components={{ Suggestion: MySuggestion }}
/>
PropTypeDescription
indexnumberZero-based suggestion index
components{ Suggestion: ComponentType }Component to render for the suggestion

Suggestion#

Pressable that sends a fixed prompt or inserts it into the composer. It renders its children and needs no suggestion context.

<ThreadPrimitive.Suggestion
  prompt="What is the weather?"
  send
  <Text>What is the weather?</Text>
</ThreadPrimitive.Suggestion>
PropTypeDescription
childrenReactNodeButton content
promptstringThe suggestion text
sendbooleanWhen true, automatically sends the message
clearComposerbooleanWhen true (default), replaces composer text; when false, appends

Loading#

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

Composable loading UI for active runs. LoadingPrimitive.Root only renders
while the current thread is running.

<LoadingPrimitive.Root gap={1}>
  <LoadingPrimitive.Spinner variant="bar" />
  <LoadingPrimitive.Text>Analyzing codebase...</LoadingPrimitive.Text>
  <LoadingPrimitive.ElapsedTime />
</LoadingPrimitive.Root>

This renders a compact status line such as [=== ] Analyzing codebase... (3s)
while the assistant is streaming.

Root#

Runtime-aware Box container that renders children only while
s.thread.isRunning is true.

PropTypeDescription
childrenReactNodeStatus row content
...restBoxPropsStandard Ink Box props

Spinner#

Animated loader indicator. variant="spinner" uses ink-spinner; the other
variants are built-in text animations.

PropTypeDescription
variant"spinner" | "dots" | "pulse" | "bar" | "bounce"Loader style (default: "spinner")
typeink-spinner typeSpinner preset used when variant="spinner"
intervalMsnumberAnimation speed for non-spinner variants (default: 120)
...restTextPropsStandard Ink Text props

Text#

Status label rendered as Ink Text.

PropTypeDescription
childrenReactNodeMessage text (default: "Thinking...")
...restTextPropsStandard Ink Text props

ElapsedTime#

Elapsed time indicator for the current run. Uses message timing metadata when
available and falls back to a local timer so it still works during manual
testing.

PropTypeDescription
format(seconds: number) => stringCustom formatter for the elapsed time label
...restTextPropsStandard Ink Text props

Composer#

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

Root#

Container Box for the composer area.

<ComposerPrimitive.Root>
  {children}
</ComposerPrimitive.Root>

Input#

Terminal line editor wired to the composer runtime. Value is managed automatically and supports cursor-aware editing.

<ComposerPrimitive.Input
  submitOnEnter
  multiLine
  placeholder="Type a message..."
  autoFocus
/>
PropTypeDescription
submitOnEnterbooleanWhether Enter sends the message (default: false)
placeholderstringPlaceholder text when empty
autoFocusbooleanAuto-focus on mount
multiLinebooleanWhen true, Enter inserts a newline unless submitOnEnter is enabled
onSubmit(text: string) => voidOverrides the default send behavior and receives the current composer text

Supported keyboard bindings:

KeyActionContext
Left / RightMove cursor by one graphemeAlways
Backspace / DeleteRemove the grapheme before / after the cursorAlways
Home / EndJump to buffer or line boundaryBuffer in single-line, line in multi-line
Up / DownMove between lines, preserving column when possibleMulti-line only
Ctrl+A / Ctrl+EMove to line start / endAlways
Ctrl+WKill the word before the cursorAlways
Ctrl+DDelete the grapheme after the cursorAlways
Ctrl+U / Ctrl+KKill from cursor to line start / end; in multi-line, Ctrl+K at end-of-line joins the next lineAlways
Ctrl+JInsert a newlineMulti-line only; swallowed in single-line so it never submits
Alt+B / Alt+FMove by word backward / forwardTerminals that emit meta-key sequences
Alt+DKill the word after the cursorTerminals that emit meta-key sequences
Shift+EnterInsert a newlineMulti-line submit mode, terminals that distinguish it

Alt+Up and Alt+Down are left to the application and do not move the input cursor.

Word boundaries and grapheme stepping use Intl.Segmenter, so emoji, accented letters, skin-tone modifiers, ZWJ sequences, and CJK ideographs each move and delete as a single character.

Some bindings depend on terminal capabilities. iTerm2, Windows Terminal, and most Linux terminals emit Alt-letter combinations as Esc-prefixed sequences that Ink decodes as meta-key input; macOS Terminal.app requires enabling "Use Option as Meta key" in its preferences before Alt+B / Alt+F / Alt+D work as expected. Shift+Enter requires a terminal that supports the CSI-u protocol (iTerm2 3.4+, kitty, foot, and similar). Terminals that conflate it with Enter fall back to the submitOnEnter behaviour.

Example with custom submit handling:

<ComposerPrimitive.Input
  multiLine
  submitOnEnter
  onSubmit={(text) => {
    console.log("submit", text);
  }}
/>

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 through a children render function.

<ComposerPrimitive.Attachments>
  {() => <MyAttachment />}
</ComposerPrimitive.Attachments>
PropTypeDescription
children(value: { attachment: Attachment }) => ReactNodeRender function called for each attachment
components{ Image?, Document?, File?, Attachment? }Deprecated, use children. Component renderers by attachment type

AddAttachment#

Pressable for an attach control. It is disabled while the composer is not editable. It has no press handler and does not accept onPress, so add attachments by calling aui.composer.addAttachment() from your own code.

<ComposerPrimitive.AddAttachment>
  <Text>[Attach]</Text>
</ComposerPrimitive.AddAttachment>

Queue#

Renders all queued composer items. Each item is wrapped in a QueueItemByIndexProvider, so QueueItemPrimitive.* can read item state and call queue actions inside the render prop. Backed by the shared ComposerPrimitiveQueue from @assistant-ui/core/react.

<ComposerPrimitive.Queue>
  {({ queueItem }) => (
    <Box>
      <QueueItemPrimitive.Text />
      <QueueItemPrimitive.Steer>
        <Text color="cyan">[Run Now]</Text>
      </QueueItemPrimitive.Steer>
      <QueueItemPrimitive.Remove>
        <Text color="red">[x]</Text>
      </QueueItemPrimitive.Remove>
    </Box>
  )}
</ComposerPrimitive.Queue>
PropTypeDescription
children(value: { queueItem: QueueItemState }) => ReactNodeRender function called for each queue item

Quote#

Renders the active composer quote preview. Children are only rendered while s.composer.quote is set, so this acts as both a container and a gate.

<ComposerPrimitive.Quote>
  <Box>
    <Text dimColor>Quoting:</Text>
    <ComposerPrimitive.QuoteText color="gray" />
    <ComposerPrimitive.QuoteDismiss>
      <Text color="red">[x]</Text>
    </ComposerPrimitive.QuoteDismiss>
  </Box>
</ComposerPrimitive.Quote>
PropTypeDescription
childrenReactNodeQuote preview content; only rendered when a quote is set
...restBoxPropsStandard Ink Box props

QuoteText#

Renders the quoted text from s.composer.quote?.text. Pass children to override the displayed value.

<ComposerPrimitive.QuoteText color="gray" />
PropTypeDescription
childrenReactNodeOverride content; defaults to s.composer.quote?.text
...restTextPropsStandard Ink Text props

QuoteDismiss#

Pressable that clears the active quote by calling aui.composer.setQuote(undefined).

<ComposerPrimitive.QuoteDismiss>
  <Text color="red">[x]</Text>
</ComposerPrimitive.QuoteDismiss>
PropTypeDescription
childrenReactNodeButton content
disabledbooleanDisable Enter and Space activation and focus
...restBoxPropsStandard Ink Box props

Conditional Rendering (Composer)#

Use AuiIf for conditional rendering based on composer state:

<AuiIf condition={(s) => s.composer.isEditing}>
  <Text>Currently editing</Text>
</AuiIf>

AttachmentByIndex#

Renders the composer attachment at a specific index and sets up its attachment context.

<ComposerPrimitive.AttachmentByIndex
  index={0}
  components={{ Attachment: MyAttachment }}
/>
PropTypeDescription
indexnumberZero-based attachment index
components{ Image?, Document?, File?, Attachment? }Component renderers by attachment type

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 { MessagePartPrimitive, MessagePrimitive } from "@assistant-ui/react-ink";

Root#

Container Box for a single message.

<MessagePrimitive.Root>
  {children}
</MessagePrimitive.Root>

Parts#

Renders message content parts through a children render function or a components map. Tool call and data parts automatically render registered toolkit renderers and data UIs (via useAssistantDataUI), falling back to components provided here. Default terminal-safe renderers are provided for text, reasoning, source, image, file, and data parts.

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

Override part rendering with app-owned components when you need richer terminal UI:

<MessagePrimitive.Parts
  components={{
    Image: () => <Text color="cyan">image available</Text>,
    Reasoning: () => null,
    data: {
      by_name: {
        weather_card: ({ data }) => <Text>{data.city}: {data.temperature}</Text>,
      },
    },
  }}
/>

Content (deprecated)#

Deprecated. Use `MessagePrimitive.Parts` instead. See the [v0.11 migration guide](/docs/migrations/v0-11) for details.

Renders message content parts using render props. Tool call and data parts automatically render registered toolkit renderers and data UIs (via 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

MessagePartPrimitive#

Composable primitives for rendering the current message part inside part context from MessagePrimitive.Parts, MessagePrimitive.PartByIndex, or PartByIndexProvider.

<MessagePrimitive.Parts
  components={{
    Text: () => (
      <>
        <MessagePartPrimitive.Text />
        <MessagePartPrimitive.InProgress>
          <Text color="yellow"> ...</Text>
        </MessagePartPrimitive.InProgress>
      </>
    ),
    File: () => <MessagePartPrimitive.File dimColor />,
  }}
/>
PrimitiveDefault terminal behavior
MessagePartPrimitive.TextRenders text with Ink <Text>
MessagePartPrimitive.InProgressRenders children only while the current part status is running
MessagePartPrimitive.ImageRenders [image: filename] or [image]; never emits image data or terminal image protocols
MessagePartPrimitive.FileRenders [file: filename mimeType] or [file: mimeType]; never prints file data
MessagePartPrimitive.SourceRenders source title and/or URL metadata
MessagePartPrimitive.ReasoningRenders the reasoning text with Ink <Text>; override or return null to suppress
MessagePartPrimitive.DataRenders [data: name] when no registered data renderer is available

PartByIndex#

Renders the message part at a specific index and sets up its part context.

<MessagePrimitive.PartByIndex
  index={0}
  components={{
    Text: () => <MessagePartPrimitive.Text />,
  }}
/>
PropTypeDescription
indexnumberZero-based part index
componentsobjectSame component map as MessagePrimitive.Parts

AttachmentByIndex#

Renders the message attachment at a specific index and sets up its attachment context.

<MessagePrimitive.AttachmentByIndex
  index={0}
  components={{ Attachment: MyAttachment }}
/>
PropTypeDescription
indexnumberZero-based attachment index
components{ Image?, Document?, File?, Attachment? }Component renderers by attachment type

Quote#

Renders the quote a message was sent with, read from s.message.metadata.custom.quote. Renders nothing when the message carries no quote. Place it above MessagePrimitive.Parts in the user message.

<MessagePrimitive.Quote>
  {({ text }) => <Text dimColor>Replying to: {text}</Text>}
</MessagePrimitive.Quote>
PropTypeDescription
children(value: { text: string; messageId: string }) => ReactNodeRender function called when the message has a quote

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 through a children render function.

<MessagePrimitive.Attachments>
  {() => <Text>[attachment]</Text>}
</MessagePrimitive.Attachments>
PropTypeDescription
children(value: { attachment: Attachment }) => ReactNodeRender function called for each attachment
components{ Image?, Document?, File?, Attachment? }Deprecated, use children. Component renderers by attachment type

Attachment#

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

Primitives for rendering individual attachments. Each primitive reads from the current attachment context (provided by ComposerPrimitive.Attachments, ComposerPrimitive.AttachmentByIndex, MessagePrimitive.Attachments, or MessagePrimitive.AttachmentByIndex).

Root#

Container Box for an attachment. Forwards all Box props.

PropTypeDescription
childrenReactNodeAttachment sub-components
...restComponentProps<typeof Box>Forwarded to the underlying Ink Box

Name#

Text component displaying s.attachment.name.

PropTypeDescription
...restComponentProps<typeof Text>Forwarded to the underlying Ink Text

Thumb#

Text component displaying a short identifier for the attachment. Renders the file extension (such as .pdf or .png) when the name has one, and the attachment type (e.g. image, document) otherwise. Passing children overrides the automatic label.

PropTypeDescription
childrenReactNodeOverrides the automatic extension/type label
...restComponentProps<typeof Text>Forwarded to the underlying Ink Text

Status#

Text component rendering the current attachment status from s.attachment.status. Uses the same visual vocabulary as ToolFallback: spinner = uploading, ? = pending action, x = error, || = paused. By default, completed attachments render nothing.

PropTypeDescription
showCompletebooleanWhen true, completed attachments render + ready instead of null
...restOmit<ComponentProps<typeof Text>, "children">Forwarded to the underlying Ink Text. Caller-supplied color overrides the per-status default. children is not accepted; Status always renders its own content.

Remove#

Pressable that calls aui.attachment.remove() when activated (Enter or Space while focused). Disabled when not focused or when the disabled prop is set.

PropTypeDescription
childrenReactNodeVisual content for the button
disabledbooleanSkip focus registration and ignore Enter and Space
...restComponentProps<typeof Box>Forwarded to the underlying Pressable's Box
<AttachmentPrimitive.Root>
  <AttachmentPrimitive.Thumb />
  <AttachmentPrimitive.Name />
  <AttachmentPrimitive.Status />
  <AttachmentPrimitive.Remove>
    <Text color="red">[x]</Text>
  </AttachmentPrimitive.Remove>
</AttachmentPrimitive.Root>

QueueItem#

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

Primitives for rendering individual queued composer items. Use inside a ComposerPrimitive.Queue render prop, where the queue item context is set up by QueueItemByIndexProvider.

Text#

Renders the queue item's text with Ink <Text>. Pass children to override the displayed value.

<QueueItemPrimitive.Text color="gray" />
PropTypeDescription
childrenReactNodeOverride content; defaults to the text parts of s.queueItem.parts
...restTextPropsStandard Ink Text props

Remove#

Pressable that removes the queue item by calling aui.queueItem.remove().

<QueueItemPrimitive.Remove>
  <Text color="red">[x]</Text>
</QueueItemPrimitive.Remove>
PropTypeDescription
childrenReactNodeButton content
disabledbooleanDisable Enter and Space activation and focus

Remaining props are forwarded to the underlying Pressable (Ink Box props).

Steer#

Pressable that promotes the queue item to run next by calling aui.queueItem.steer().

<QueueItemPrimitive.Steer>
  <Text color="cyan">[Run Now]</Text>
</QueueItemPrimitive.Steer>
PropTypeDescription
childrenReactNodeButton content
disabledbooleanDisable Enter and Space activation and focus

Remaining props are forwarded to the underlying Pressable (Ink Box props).

ActionBar#

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

Copy#

Pressable that copies the message content. Pass a platform clipboard writer through copyToClipboard. Supports function-as-children for copy state feedback.

<InstallCommand npm={["clipboardy"]} />

import clipboard from "clipboardy";

<ActionBarPrimitive.Copy
  copiedDuration={3000}
  copyToClipboard={clipboard.write}
  {({ isCopied }) => <Text>{isCopied ? "[Copied!]" : "[Copy]"}</Text>}
</ActionBarPrimitive.Copy>
PropTypeDescription
copiedDurationnumberDuration in ms to show "copied" state (default: 3000)
copyToClipboard(text: string) => void | Promise<void>Platform clipboard writer

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 through a children render function. Shared from @assistant-ui/core/react.

<ChainOfThoughtPrimitive.Root>
  <ChainOfThoughtPrimitive.AccordionTrigger>
    <Text dimColor>[Toggle reasoning]</Text>
  </ChainOfThoughtPrimitive.AccordionTrigger>
  <ChainOfThoughtPrimitive.Parts>
    {({ part }) => part.type === "reasoning" ? <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} />;
    return null;
  }}
</MessagePrimitive.Parts>
PropTypeDescription
...partToolCallMessagePartPropsThe tool call part fields (toolName, args, argsText, result, status, and so on), spread onto the component
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. Accepts either a unified diff patch string or old/new file content for inline comparison.

When a replacement is rendered as an equal-length adjacent -/+ run, DiffView also highlights the changed word spans inside those lines. Prefixes, line numbers, and folding are unaffected. This intra-line behavior is internal to DiffView; DiffPrimitive.Line remains a line-level primitive.

// 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 parsed diff files to child components. It does not parse a patch or compare file contents; pass already parsed files, or use DiffView to render from a patch string or file contents.

<DiffPrimitive.Root files={files}>
  {children}
</DiffPrimitive.Root>
PropTypeDescription
filesParsedFile[]Parsed files (oldName, newName, lines, additions, deletions) read by the child primitives

Header#

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 files={files}>
  <Box flexDirection="column">
    <DiffPrimitive.Header />
    <DiffPrimitive.Stats />
    <DiffPrimitive.Content
      showLineNumbers
      contextLines={2}
    />
  </Box>
</DiffPrimitive.Root>

StatusBar#

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

Primitives for rendering a compact terminal status bar showing run state, model, message count, token usage, and latency. All sub-components read directly from the active thread state.

Root#

Container Box for the status bar. Forwards all Box props; pass gap to space its children.

PropTypeDescription
childrenReactNodeStatus bar sub-components
...restComponentProps<typeof Box>Forwarded to the underlying Ink Box

ModelName#

Text component displaying the current model name. The name is supplied as a prop. The primitive does not infer it from state.

PropTypeDescription
nameReactNodeModel label to render (default: "unknown")
...restComponentProps<typeof Text>Forwarded to the underlying Ink Text

MessageCount#

Text component displaying the number of messages on the current thread, formatted as N msgs by default. Renders nothing when the thread is empty.

PropTypeDescription
format(count: number) => stringCustom formatter for the count (default: `${count} msgs`)
...restComponentProps<typeof Text>Forwarded to the underlying Ink Text

TokenCount#

Text component summing metadata.timing.tokenCount across all assistant messages on the thread, formatted with toLocaleString() + " tokens" by default. Renders nothing when the total is 0.

PropTypeDescription
format(tokens: number) => stringCustom formatter for the total (default: `${tokens.toLocaleString()} tokens`)
...restComponentProps<typeof Text>Forwarded to the underlying Ink Text

Latency#

Text component displaying metadata.timing.tokensPerSecond from the most recent assistant message, formatted as N tok/s (rounded) by default. Renders nothing when the field is missing.

PropTypeDescription
format(tokensPerSecond: number) => stringCustom formatter (default: `${Math.round(tokensPerSecond)} tok/s`)
...restComponentProps<typeof Text>Forwarded to the underlying Ink Text

Status#

Text component reporting the thread's run state. Resolves to "running" while s.thread.isRunning, otherwise inspects the most recent assistant message: "error" when its status is incomplete with reason error, "cancelled" when reason is cancelled, and "idle" for every other case. Pass color (or any Text prop) to style the label.

PropTypeDescription
format(status: "idle" | "running" | "error" | "cancelled") => stringCustom label formatter (default: returns the state string verbatim)
...restComponentProps<typeof Text>Forwarded to the underlying Ink Text
<StatusBarPrimitive.Root gap={1}>
  <StatusBarPrimitive.Status />
  <StatusBarPrimitive.ModelName name="gpt-6-luna" />
  <StatusBarPrimitive.MessageCount />
  <StatusBarPrimitive.TokenCount />
  <StatusBarPrimitive.Latency />
</StatusBarPrimitive.Root>

TextInput#

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

Store-free, controlled single/multi-line text field. Ink ships no native text input, so this is the terminal equivalent of the DOM <textarea> or React Native's <TextInput>: the editing engine (buffer, grapheme-aware cursor, and the full emacs/arrow keymap) without any runtime or composer coupling. ComposerPrimitive.Input is a thin adapter that wraps TextInput and binds it to the composer store. Use TextInput directly when you need an editable field outside the thread composer (for example, editing a value before confirming an action).

It is controlled on value/onChange, so an external owner can override the text at any time (the buffer absorbs the echo without thrashing the cursor). An owner may also transform the emitted text in onChange (uppercase, mask, reject); the buffer applies the corrected value while keeping the cursor at the edit position, snapped to a grapheme boundary. A replacement that does not respond to an edit (a programmatic set while idle) still places the cursor at the end:

const [command, setCommand] = useState(approval.action.command);

<TextInput
  value={command}
  onChange={setCommand}
  submitOnEnter
  onSubmit={(text) => runEdited(text)}
/>
PropTypeDescription
valuestringCurrent text (controlled)
onChange(text: string) => voidCalled with the next text whenever the buffer changes
onSubmit(text: string) => voidCalled on Enter when submitOnEnter is set, with the text as displayed; owners that validate in onChange should validate again in onSubmit
submitOnEnterbooleanWhether Enter calls onSubmit (default: false)
placeholderstringPlaceholder text when empty (default: "")
autoFocusbooleanAuto-focus on mount (default: true)
multiLinebooleanWhen true, Enter inserts a newline unless submitOnEnter is enabled
...restComponentProps<typeof Box>Forwarded to the underlying Ink Box

Keyboard bindings, grapheme handling, and terminal-capability caveats are identical to ComposerPrimitive.Input.

Documents
AG-UI Message History Management
CHANGELOG
CHANGELOG
CHANGELOG
external-store
overview
README
AG-UI Streaming State Management
hooks
index
introduction
overview
part-1
README
README
stockbroker
AI SDK Integration
ai-sdk
assistant-runtime-provider
expo
hooks
index
index
index
introduction
migration
migration
overview
part-2
README
README
README
README
README
README
README
README
stockbroker
Cloud Message Pagination
index
Generative UI Date and Time Controls
In-Memory Thread Lifecycle
CHANGELOG
CHANGELOG
CHANGELOG
Local Runtime Message History
CHANGELOG
Markdown Preprocessing and Repair
CHANGELOG
CHANGELOG
MCP App Tool UI Rendering
CHANGELOG
CHANGELOG
message
MCP OAuth State Management
MessageRepository Branch Pointer Invariant
Next.js Cache Components
Remote Thread List Runtime
adapters
CHANGELOG
CHANGELOG
hooks
index
README
README
thread-runtime
Render Prop Leakage
Run Cancellation and Abort Signal Propagation
CHANGELOG
CHANGELOG
CHANGELOG
CHANGELOG
README
Run Telemetry Model ID Extraction
SSR-Safe Thread ID Generation
Stream Decoder Buffer Limits
CHANGELOG
CHANGELOG
StreamdownTextPrimitive
index
Streaming Markdown Rendering
CHANGELOG
CHANGELOG
index
introduction
part-1
AGENTS
CHANGELOG
CHANGELOG
CHANGELOG
CHANGELOG
CHANGELOG
CHANGELOG
CHANGELOG
CHANGELOG
CHANGELOG
CHANGELOG
CHANGELOG
CHANGELOG
CHANGELOG
CHANGELOG
CHANGELOG
CHANGELOG
CHANGELOG
CODE_OF_CONDUCT
CONTRIBUTING
README
README
README
README
README
README
README
README
README
README
README
README
README
README
README
README
README
README
README
README
README
README
README
README
README
README
README
README
README
README
README
README
README
README
README
README
README
README_LANGGRAPH
SECURITY
SPEC
SPEC_AssistantFrame
SPEC_ModelContextRegistry
action-bar
action-bar-more
adapters
artifacts
assistant-if
assistant-modal
assistant-modal
assistant-runtime
assistant-sidebar
attachment
attachment
attachment-runtime
branch-picker
chatgpt
claude
composer
composer-runtime
composition
context-display
custom-backend
custom-backend
error
file
form-demo
grok
heat-graph
hooks
image
index
index
index
index
index
index
index
index
index
markdown
mem0
mermaid
message-part
message-part-runtime
message-runtime
message-timing
modal
model-selector
part-3
perplexity
pick-a-runtime
primitives
primitives
quote
reasoning
selection-toolbar
sources
suggestion
syntax-highlighting
thread
thread
thread-list
thread-list
thread-list-item
thread-list-item-more
thread-list-item-runtime
thread-list-runtime
tool-fallback
tool-group
tw-shimmer
v4-legacy
v5-legacy