Primitives for the text input, send button, and attachments.
The user interface to add new messages or edit existing ones.
Dual Use! A Composer placed directly inside a Thread will compose new
messages. A Composer placed inside a Message will edit that message.
Anatomy
import { ComposerPrimitive } from "@assistant-ui/react";
// creating a new message
const Composer = () => (
<ComposerPrimitive.Root>
<ComposerPrimitive.AttachmentDropzone>
<ComposerPrimitive.Quote>
<ComposerPrimitive.QuoteText />
<ComposerPrimitive.QuoteDismiss />
</ComposerPrimitive.Quote>
<ComposerPrimitive.Attachments />
<ComposerPrimitive.AddAttachment />
<ComposerPrimitive.Input />
<ComposerPrimitive.Send />
</ComposerPrimitive.AttachmentDropzone>
</ComposerPrimitive.Root>
);
// editing an existing message
const EditComposer = () => (
<ComposerPrimitive.Root>
<ComposerPrimitive.Input />
<ComposerPrimitive.Send />
<ComposerPrimitive.Cancel />
</ComposerPrimitive.Root>
);
// with voice input (dictation)
const ComposerWithDictation = () => (
<ComposerPrimitive.Root>
<ComposerPrimitive.Input />
<AuiIf condition={(s) => s.composer.dictation == null}>
<ComposerPrimitive.Dictate />
</AuiIf>
<AuiIf condition={(s) => s.composer.dictation != null}>
<ComposerPrimitive.StopDictation />
</AuiIf>
<ComposerPrimitive.Send />
</ComposerPrimitive.Root>
);API Reference
Root
Contains all parts of the composer.
This primitive renders a <form> element unless asChild is set.
ComposerRootPropsasChild: boolean= falseChange the default rendered element for the one passed as a child, merging their props and behavior.
Read the Composition guide for more details.
Input
The text input field for the user to type a new message.
This primitive renders a <textarea> element unless asChild is set.
ComposerPrimitiveInputPropsasChild: boolean= falseChange the default rendered element for the one passed as a child, merging their props and behavior.
Read the Composition guide for more details.
submitMode: "enter" | "ctrlEnter" | "none"= "enter"Controls how the Enter key submits messages. "enter": plain Enter submits (Shift+Enter for newline). "ctrlEnter": Ctrl/Cmd+Enter submits (plain Enter for newline). "none": keyboard submission disabled.
cancelOnEscape: boolean= trueWhether to cancel message composition when Escape is pressed.
unstable_focusOnRunStartunstable: boolean= trueWhether to automatically focus the input when a new run starts.
unstable_focusOnScrollToBottomunstable: boolean= trueWhether to automatically focus the input when scrolling to bottom.
unstable_focusOnThreadSwitchedunstable: boolean= trueWhether to automatically focus the input when switching threads.
addAttachmentOnPaste: boolean= trueWhether to automatically add pasted files as attachments.
Keyboard Shortcuts
Default (submitMode="enter"):
| Key | Description |
|---|---|
| Enter | Sends the message. |
| Shift + Enter | Inserts a newline. |
| Escape | Sends a cancel action. |
With submitMode="ctrlEnter":
| Key | Description |
|---|---|
| Ctrl/Cmd + Enter | Sends the message. |
| Enter | Inserts a newline. |
| Escape | Sends a cancel action. |
Send
The button to send the message.
This primitive renders a <button> element unless asChild is set.
ComposerPrimitiveSendPropsasChild: boolean= falseChange the default rendered element for the one passed as a child, merging their props and behavior.
Read the Composition guide for more details.
Cancel
Sends a cancel action.
In edit composers, this action exits the edit mode. In thread composers, this action stops the current run.
This primitive renders a <button> element unless asChild is set.
ComposerPrimitiveCancelPropsasChild: boolean= falseChange the default rendered element for the one passed as a child, merging their props and behavior.
Read the Composition guide for more details.
Attachments
Renders attachments. This primitive renders a separate component for each attachment.
ComposerPrimitiveAttachmentsPropscomponents?: ComposerAttachmentsComponentsThe component to render for each attachment.
ComposerPrimitiveAttachmentsProps['components']Image?: ComponentTypeThe component to render for each image attachment.
Document?: ComponentTypeThe component to render for each document attachment.
File?: ComponentTypeThe component to render for each file attachment.
Attachment?: ComponentTypeThe fallback component to render for each attachment type.
AttachmentByIndex
Renders a single attachment at the specified index within the composer.
<ComposerPrimitive.AttachmentByIndex
index={0}
components={{
Image: MyImageAttachment,
Document: MyDocumentAttachment
}}
/>ComposerPrimitive.AttachmentByIndex.Propsindex: numberThe index of the attachment to render.
components?: ComposerAttachmentsComponentsThe components to render for the attachment.
AddAttachment
Renders a button to add an attachment.
This primitive renders a <button> element unless asChild is set.
ComposerPrimitiveAddAttachmentPropsasChild: boolean= falseChange the default rendered element for the one passed as a child, merging their props and behavior.
Read the Composition guide for more details.
multiple: boolean | undefined= trueAllow selecting multiple attachments at the same time.
AttachmentDropzone
A drag-and-drop zone that accepts file drops and adds them as attachments to the composer.
When a file is dragged over the zone, a data-dragging="true" attribute is set on the element, which can be used for styling the active drag state.
This primitive renders a <div> element unless asChild is set.
<ComposerPrimitive.AttachmentDropzone className="relative data-[dragging]:ring-2">
<ComposerPrimitive.Input />
<ComposerPrimitive.Send />
</ComposerPrimitive.AttachmentDropzone>ComposerPrimitiveAttachmentDropzonePropsasChild: boolean= falseChange the default rendered element for the one passed as a child, merging their props and behavior.
Read the Composition guide for more details.
disabled?: boolean | undefinedWhen true, drag-and-drop is disabled and files will not be added on drop.
Dictate
Renders a button to start dictation to convert voice to text.
Requires a DictationAdapter to be configured in the runtime.
This primitive renders a <button> element unless asChild is set.
ComposerPrimitiveDictatePropsasChild: boolean= falseChange the default rendered element for the one passed as a child, merging their props and behavior.
Read the Composition guide for more details.
StopDictation
Renders a button to stop the current dictation session.
Only rendered when dictation is active.
This primitive renders a <button> element unless asChild is set.
ComposerPrimitiveStopDictationPropsasChild: boolean= falseChange the default rendered element for the one passed as a child, merging their props and behavior.
Read the Composition guide for more details.
DictationTranscript
Renders the current interim (partial) transcript while dictation is active.
Note: By default, interim transcripts are displayed directly in the composer input (like native dictation). This component is for advanced customization when you want to display the interim transcript separately (e.g., in a different style or location).
Only renders when there is an active interim transcript (returns null otherwise).
This primitive renders a <span> element.
{/* Optional: Display interim transcript separately with custom styling */}
<AuiIf condition={(s) => s.composer.dictation != null}>
<div className="dictation-preview">
<ComposerPrimitive.DictationTranscript className="italic text-muted" />
</div>
</AuiIf>Quote
A container for displaying a quote preview in the composer. Only renders when a quote is set via composer.setQuote().
This primitive renders a <div> element.
<ComposerPrimitive.Quote className="flex items-start gap-2 bg-muted/60 px-3 py-2">
<ComposerPrimitive.QuoteText className="line-clamp-2 flex-1 text-sm" />
<ComposerPrimitive.QuoteDismiss>×</ComposerPrimitive.QuoteDismiss>
</ComposerPrimitive.Quote>QuoteText
Renders the quoted text content. Only renders when a quote is set.
This primitive renders a <span> element.
QuoteDismiss
A button that clears the current quote from the composer by calling setQuote(undefined).
This primitive renders a <button> element unless asChild is set.
ComposerPrimitiveQuoteDismissPropsasChild: boolean= falseChange the default rendered element for the one passed as a child, merging their props and behavior.
Read the Composition guide for more details.
See the Quoting guide for a complete walkthrough including the floating selection toolbar and backend handling.
Conditional Rendering
Use AuiIf for conditional rendering based on composer state:
import { AuiIf } from "@assistant-ui/react";
<AuiIf condition={(s) => s.composer.isEditing}>
{/* rendered if message is being edited */}
</AuiIf>
<AuiIf condition={(s) => s.composer.dictation != null}>
{/* rendered if dictation is active */}
</AuiIf>Trigger Popover Primitives (Unstable)
These primitives are under the Unstable_ prefix and may change without notice.
Unified primitives for any character-triggered popover (@ mentions, / slash commands, : emoji, etc.). Multiple triggers coexist under a single TriggerPopoverRoot. See the Mentions guide and Slash Commands guide for full documentation and the ComposerTriggerPopover component for a pre-built implementation.
Anatomy
import { ComposerPrimitive } from "@assistant-ui/react";
import { unstable_defaultDirectiveFormatter } from "@assistant-ui/core";
const Composer = () => (
<ComposerPrimitive.Unstable_TriggerPopoverRoot>
<ComposerPrimitive.Root>
<ComposerPrimitive.Input />
<ComposerPrimitive.Unstable_TriggerPopover
char="@"
adapter={mention.adapter}
>
<ComposerPrimitive.Unstable_TriggerPopover.Directive
{...mention.directive}
/>
<ComposerPrimitive.Unstable_TriggerPopoverCategories>
{(categories) =>
categories.map((cat) => (
<ComposerPrimitive.Unstable_TriggerPopoverCategoryItem
key={cat.id}
categoryId={cat.id}
>
{cat.label}
</ComposerPrimitive.Unstable_TriggerPopoverCategoryItem>
))
}
</ComposerPrimitive.Unstable_TriggerPopoverCategories>
<ComposerPrimitive.Unstable_TriggerPopoverItems>
{(items) =>
items.map((item) => (
<ComposerPrimitive.Unstable_TriggerPopoverItem
key={item.id}
item={item}
>
{item.label}
</ComposerPrimitive.Unstable_TriggerPopoverItem>
))
}
</ComposerPrimitive.Unstable_TriggerPopoverItems>
<ComposerPrimitive.Unstable_TriggerPopoverBack>
Back
</ComposerPrimitive.Unstable_TriggerPopoverBack>
</ComposerPrimitive.Unstable_TriggerPopover>
</ComposerPrimitive.Root>
</ComposerPrimitive.Unstable_TriggerPopoverRoot>
);Unstable_TriggerPopoverRoot
Root provider that groups one or more TriggerPopover declarations. Owns the shared ComposerInputPluginRegistry that routes cursor and keyboard events to each registered trigger.
No props.
Unstable_TriggerPopover
Declares a trigger and renders its popover container. Only renders its DOM (and children) while this trigger is active in the composer input. Each declaration has an isolated scope — sub-primitives placed inside read state from this declaration only.
| Prop | Type | Default | Description |
|---|---|---|---|
char | string | — | Character(s) that activate the trigger (e.g. "@", "/") — unique within the root |
adapter | Unstable_TriggerAdapter | — | Provides categories, items, and search |
Selection behavior is declared by rendering exactly one behavior sub-primitive inside the TriggerPopover:
Unstable_TriggerPopover.Directive— inserts:type[label]{name=id}into the composer on selection (mention behavior).Unstable_TriggerPopover.Action— firesonExecuteon selection; leaves a directive chip as an audit trail by default.
Renders a <div> with role="listbox" when open.
Unstable_TriggerPopover.Directive
Behavior sub-primitive. Placed inside a <Unstable_TriggerPopover>. Renders nothing — registers an "directive" behavior with its parent. Exactly one behavior sub-primitive is allowed per TriggerPopover.
| Prop | Type | Default | Description |
|---|---|---|---|
formatter | Unstable_DirectiveFormatter | unstable_defaultDirectiveFormatter | Serializes the selected item into composer text (and parses it back). |
onInserted | (item) => void | — | Fires after the directive text has been inserted. |
Unstable_TriggerPopover.Action
Behavior sub-primitive. Placed inside a <Unstable_TriggerPopover>. Renders nothing — registers an "action" behavior with its parent. Exactly one behavior sub-primitive is allowed per TriggerPopover.
| Prop | Type | Default | Description |
|---|---|---|---|
formatter | Unstable_DirectiveFormatter | unstable_defaultDirectiveFormatter | Serializes the audit-trail chip (unused when removeOnExecute is true). |
onExecute | (item) => void | — | Called the moment an item is selected — typically routes to a handler map. Required. |
removeOnExecute | boolean | false | When true, strips the trigger text from the composer instead of leaving a chip. |
Unstable_TriggerBehavior
Union type returned by the two behavior sub-primitives and consumed internally by the popover resources. Exported for advanced use cases (e.g. custom sub-primitives).
import type { Unstable_TriggerBehavior } from "@assistant-ui/react";
// Equivalent shape:
// Unstable_TriggerBehavior =
// | { kind: "directive"; formatter: Unstable_DirectiveFormatter; onInserted?: (item) => void }
// | { kind: "action"; formatter: Unstable_DirectiveFormatter; onExecute: (item) => void; removeOnExecute?: boolean }Unstable_TriggerPopoverCategories
Renders the top-level category list. Accepts a render function (categories) => ReactNode. Hidden when a category is selected or when in search mode.
Unstable_TriggerPopoverCategoryItem
A button that drills into a category. Renders role="option" with automatic data-highlighted and aria-selected when keyboard-navigated.
| Prop | Type | Description |
|---|---|---|
categoryId | string | The category to select on click |
Unstable_TriggerPopoverItems
Renders the item list for the active category or search results. Accepts a render function (items) => ReactNode. Hidden when no category is selected and not in search mode.
Unstable_TriggerPopoverItem
A button that selects an item. Renders role="option" with automatic data-highlighted and aria-selected when keyboard-navigated.
| Prop | Type | Description |
|---|---|---|
item | Unstable_TriggerItem | The item to select on click |
index | number | Optional index override for highlight matching |
Unstable_TriggerPopoverBack
A button that navigates back from items to the category list. Only renders when a category is active.
unstable_useTriggerPopoverScopeContext
Hook to access the popover state and actions for the nearest enclosing Unstable_TriggerPopover.
const {
open, // boolean — whether popover is visible
query, // string — text after the trigger character
isSearchMode, // boolean — whether showing search results
highlightedIndex,
categories,
items,
activeCategoryId,
selectCategory,
selectItem,
goBack,
close,
handleKeyDown,
} = unstable_useTriggerPopoverScopeContext();unstable_useTriggerPopoverTriggers
Hook to iterate every trigger registered under the current Unstable_TriggerPopoverRoot. Intended for input-level integrations (e.g. Lexical DirectivePlugin). Returns ReadonlyMap<string, Unstable_RegisteredTrigger>.
unstable_useMentionAdapter
Returns a spreadable { adapter, directive } bundle for @ mentions. Reads tools registered via useAssistantTool, accepts explicit items, and supports multi-category drill-down.
import {
unstable_useMentionAdapter,
type Unstable_Mention,
} from "@assistant-ui/react";
function MyComposer() {
// Default: tools from model context as a single "Tools" category
const mention = unstable_useMentionAdapter();
return (
<ComposerTriggerPopover char="@" {...mention} />
);
}// Multi-category + tools appended as their own category
const mention = unstable_useMentionAdapter({
categories: [
{ id: "users", label: "Users", items: [/* ... */] },
{ id: "files", label: "Files", items: [/* ... */] },
],
includeModelContextTools: true,
});Options:
| Option | Type | Description |
|---|---|---|
items | Unstable_Mention[] | Flat list; ignored when categories is set. |
categories | { id, label, items: Unstable_Mention[] }[] | Drill-down groups. |
includeModelContextTools | boolean | { category?, formatLabel?, icon? } | Tools integration. Defaults to true iff neither items nor categories. |
formatter | Unstable_DirectiveFormatter | Override directive serialization. |
onInserted | (item) => void | Fires after the directive is inserted. |
iconMap | Record<string, Unstable_IconComponent> | metadata.icon / category id → React component. |
fallbackIcon | Unstable_IconComponent | Fallback when no iconMap entry matches. |
Returns { adapter, directive, iconMap?, fallbackIcon? }.
unstable_useSlashCommandAdapter
Returns a spreadable { adapter, action } bundle for slash commands. Commands are declared as data + inline execute callbacks; execute lives in the hook closure and is never attached to the returned Unstable_TriggerItem, keeping items JSON-serializable.
import {
unstable_useSlashCommandAdapter,
type Unstable_SlashCommand,
} from "@assistant-ui/react";
const SLASH_COMMANDS: readonly Unstable_SlashCommand[] = [
{ id: "summarize", execute: () => runSummarize(), icon: "FileText" },
{ id: "translate", execute: () => runTranslate(), icon: "Languages" },
];
function MyComposer() {
const slash = unstable_useSlashCommandAdapter({ commands: SLASH_COMMANDS });
return (
<ComposerTriggerPopover char="/" {...slash} />
);
}Pass removeOnExecute: true on the hook options to strip the trigger text from the composer after executing. The hook also accepts iconMap / fallbackIcon options that flow into the returned bundle — {...slash} spreads them into ComposerTriggerPopover.