v1.5

Prompt Input

Chat composer for AI products with an autosizing field, toolbar, run states, attachments, a follow-up queue and inline mentions.

Pro

Answers can be wrong. Check anything important.

Description

PromptInput is the message box at the bottom of an AI chat. It is a compound component: the root renders a <div> that owns the text, the run status and the layout, Shell draws the bordered surface, TextArea is an autosizing <textarea>, and Toolbar holds icon Actions plus a Send button that turns into a stop control while a response streams.

Use it for chat threads, copilots docked in a side panel, "ask about this page" boxes and follow-up fields under a generated answer. Two add-ons ship as separate registry items so the base composer stays light: PromptInputQueue lists follow-up prompts waiting for the current run (built on Motion), and PromptInputTokens swaps the textarea for a field with inline @mentions and /commands (built on the React Aria TokenField).

Don't use it for plain forms. A comment box or a feedback field wants Textarea inside Field, and a single-line search wants Input or Command. If you only need the starter prompts shown above an empty chat, that is Prompt Suggestion.

PromptInput is a Create UI Pro component. Install it with a Pro seat.

Installation

pnpm dlx @create-ui/cli add prompt-input

Anatomy

<PromptInput>
  <PromptInputQueue>
    <PromptInputQueue.List>
      <PromptInputQueue.Item>
        <PromptInputQueue.Item.Handle />
        <PromptInputQueue.Item.Body>
          <PromptInputQueue.Item.Icon />
          <PromptInputQueue.Item.Content />
          <PromptInputQueue.Item.Description />
        </PromptInputQueue.Item.Body>
        <PromptInputQueue.Item.Actions>
          <PromptInputQueue.Item.Action />
          <PromptInputQueue.Item.Remove />
          <PromptInputQueue.Item.More />
        </PromptInputQueue.Item.Actions>
        <PromptInputQueue.Item.Attachments>
          <PromptInputQueue.Item.AttachmentsOverflow />
        </PromptInputQueue.Item.Attachments>
      </PromptInputQueue.Item>
    </PromptInputQueue.List>
  </PromptInputQueue>
  <PromptInput.Shell>
    <PromptInput.Content>
      <PromptInput.Attachments />
      <PromptInput.TextArea />
    </PromptInput.Content>
    <PromptInput.Toolbar>
      <PromptInput.ToolbarStart>
        <PromptInput.Action />
      </PromptInput.ToolbarStart>
      <PromptInput.ToolbarEnd>
        <PromptInput.Send />
      </PromptInput.ToolbarEnd>
    </PromptInput.Toolbar>
  </PromptInput.Shell>
  <PromptInput.Footer />
</PromptInput>

With tokens, PromptInputTokens wraps the content and the suggestion popover, and PromptInputTokens.Input replaces PromptInput.TextArea:

<PromptInput.Shell>
  <PromptInputTokens>
    <PromptInput.Content>
      <PromptInputTokens.Input tokens={tokens} />
    </PromptInput.Content>
    <PromptInputTokens.Suggestions tokens={tokens} />
  </PromptInputTokens>
  <PromptInput.Toolbar />
</PromptInput.Shell>

Usage

import { PromptInput } from "@/components/ui/prompt-input"
<PromptInput value={value} onValueChange={setValue} onSubmit={send}>
  <PromptInput.Shell>
    <PromptInput.Content>
      <PromptInput.TextArea />
    </PromptInput.Content>
    <PromptInput.Toolbar>
      <PromptInput.ToolbarEnd>
        <PromptInput.Send />
      </PromptInput.ToolbarEnd>
    </PromptInput.Toolbar>
  </PromptInput.Shell>
</PromptInput>

Examples

Every example is a Pro preview (marked with a Pro badge).

Layouts

layout="stacked" puts the toolbar under the field. inline and compact start as a single 48px row with the actions beside the text, then grow into the stacked shape once the text wraps or an attachment arrives. compact also clips the collapsed row to one line, which suits a follow-up field under an answer.

Pro

Variants

variant="primary" is a white surface with a border for page backgrounds. secondary fills the shell with bg-weakest, for composers sitting on an already white panel.

Pro

Sizes

size follows the Textarea scale. xs, sm and md set the type to text-ui-control-md, -lg and -xl and grow the minimum field height. Icon buttons are 32px, and 40px on a stacked md composer. Single-row layouts keep 32px buttons at every size so the row stays 48px.

Pro

Run status

status drives the send button. submitted shows a spinner and streaming a stop icon, both calling onStop when you pass it. error turns the button red and relabels it "Retry". The field locks while running unless lockInputOnRun={false}, and a visually hidden live region announces each status (override the text with statusLabels).

Pro

ready

submitted

Sending message

streaming

Generating response

error

Response failed

Pass allowSubmitWhileRunning to keep Send and Enter submitting while a response streams, which is how the queue example below collects follow-ups. Send switches back to a stop button whenever the field is empty.

Toolbar

PromptInput.Action is an icon-only ghost Button sized for the composer. Give each one an aria-label, and a tooltip when the icon is not obvious. Any other control, like a model picker Button, can sit in ToolbarStart or ToolbarEnd.

Pro

Attachments

Render ChatAttachment tiles inside PromptInput.Attachments. The row collapses when it is empty, and in the single-row layouts a non-empty row expands the composer. A composer with attachments and no text can still be sent. For the file picker, drop target and image paste, see the ChatAttachmentInput example on the Chat Attachment page.

Pro
q3-brief.pdf
243 KB
signups.csv
18 KB
interview-notes.md
6 KB

Queue

PromptInputQueue is a separate item: install it with npx @create-ui/cli add prompt-input-queue. It renders a card above the shell for prompts waiting on the current run. Pass values and onReorder to List and a value to each Item to make rows draggable by their Handle. Focus a handle and press ArrowUp or ArrowDown to move a row from the keyboard.

Pro
  • Add tests for the retry logic.

  • Update the README with the new environment variables.

  • Tighten the types on the webhook payload.

Generating response

Row actions show on hover and focus by default and stay visible on touch screens. Set actionsVisibility="always" to pin them.

Mentions and commands

PromptInputTokens is a separate item: install it with npx @create-ui/cli add prompt-input-tokens. It needs react-aria-components 1.21 or newer. usePromptInputTokens tracks the field value and the active trigger. Render a DropdownMenu inside PromptInputTokens.Suggestions and call tokens.insertToken from onAction to turn the typed query into a token.

Pro

Keep PromptInput.Toolbar outside PromptInputTokens. The wrapper is a React Aria Autocomplete, so any menu inside it joins the suggestion list. For a composer docked to the bottom of the viewport, pass placement="top start" to Suggestions.

Accessibility

KeyDescription
EnterSubmits. Ignored while an IME composition is open, when there is nothing to send, and while running unless allowSubmitWhileRunning.
Shift + EnterInserts a newline.
TabMoves between the field, toolbar actions, the send button and queue controls.
Enter / SpaceActivates the focused action, send or stop button.
ArrowUp / ArrowDownOn a focused queue Handle, moves the row. ArrowLeft / ArrowRight with axis="x".
ArrowUp / ArrowDownWith the token suggestions open, moves through the menu while the caret stays in the field.
EscapeCloses the token suggestions until the caret leaves that trigger.

ARIA notes:

  • The textarea is labelled "Message" by default. Pass aria-label (or aria-labelledby) to TextArea or PromptInputTokens.Input to name it after your assistant.
  • Send switches its name between sendLabel ("Send message"), stopLabel ("Stop generating") and retryLabel ("Retry"). Its icons are aria-hidden.
  • The root renders a role="status" region that announces submitted, streaming and error. Set a status to "" in statusLabels to silence it.
  • Your own handler runs first: call event.preventDefault() in TextArea's onKeyDown to replace the Enter behavior, for example to send with Cmd + Enter only.
  • PromptInput.Action renders no name of its own. Give every icon-only action an aria-label.
  • The queue Handle is labelled "Reorder queued prompt" and sets aria-keyshortcuts. Each keyboard move is announced ("Moved to position 2 of 3"), customizable with getMoveAnnouncement on List.
  • Label queue Remove buttons with the prompt they remove, since several rows share the default "Remove from queue".
  • The token field is a React Aria TokenField with aria-placeholder mirrored from placeholder. The suggestions use the Menu pattern with virtual focus. See the React Aria Autocomplete docs.
  • The queue's layout animation and the composer transitions are skipped under prefers-reduced-motion.

Styling

Tailwind override: pass className to any part. Classes merge with the component's CVA classes via cn().

<PromptInput.Shell className="shadow-neutral-sm" />

Data slots and attributes: the component sets these for CSS targeting:

  • data-slot="prompt-input" on the root, with data-layout, data-size, data-variant, data-status, data-disabled and data-expanded (single-row layouts that have grown).
  • data-slot="prompt-input-status" on the visually hidden live region.
  • data-slot="prompt-input-shell" on the surface, with data-mode ("stacked", "inline", "inlineExpanded", "compactCollapsed", "compactExpanded"). It styles data-state="dragging" when a ChatAttachmentInput.Dropzone wraps it with asChild.
  • data-slot="prompt-input-content", data-slot="prompt-input-attachments" and data-slot="prompt-input-textarea" on the field column.
  • data-slot="prompt-input-toolbar", data-slot="prompt-input-toolbar-start" and data-slot="prompt-input-toolbar-end" on the toolbar.
  • data-slot="prompt-input-action" on each action and data-slot="prompt-input-send" on the send button, with data-status.
  • data-slot="prompt-input-footer" on the footer.
  • data-slot="prompt-input-queue" on the queue card, with data-actions-visibility.
  • data-slot="prompt-input-queue-list" on the scroll container and data-slot="prompt-input-queue-item" on each row.
  • data-slot="prompt-input-queue-item-handle" on the handle, with data-reorder-enabled.
  • data-slot="prompt-input-queue-item-body", -icon, -content, -description, -attachments, -attachments-overflow, -actions, -action, -remove and -more on the row parts.
  • data-slot="prompt-input-token-input" on the token field, with data-empty, data-placeholder and React Aria's data-disabled.
  • data-slot="prompt-input-token" on each token, with data-selected and data-disabled.
  • data-slot="prompt-input-token-suggestions" on the popover, with React Aria's data-placement, data-entering and data-exiting.
[data-slot="prompt-input"][data-status="streaming"]
  [data-slot="prompt-input-shell"] {
  /* ... */
}

The root sets --prompt-input-max-height from maxHeight. The stacked field and the token field cap their height with it and scroll past it.

  • Textarea: a plain multi-line form field with label, hint and validation, without run states.
  • Prompt Suggestion: starter prompts for the empty state above the composer.
  • Chat Attachment: the file tiles, picker and drop target that plug into PromptInput.Attachments.
  • Command: a search-first palette for picking actions, not for writing messages.

API Reference

PromptInput

The root. Owns the text, status and layout, and renders a <div>. Extends React.ComponentProps<"div">. Also exported as PromptInputRoot.

Props

PropTypeDefaultDescription
valuestring-Controlled text.
defaultValuestring""Initial text when uncontrolled.
onValueChange(value: string) => void-Called on every text change.
onSubmit() => void-Called by Enter and Send when a submit is allowed.
onStop() => void-Turns Send into a stop button while running.
status"ready" | "submitted" | "streaming" | "error""ready"Run state.
statusLabelsPartial<Record<PromptInputStatus, string>>-Live region text per status. Defaults announce submitted, streaming and error.
isDisabledbooleanfalseDisables the field and every control.
lockInputOnRunbooleantrueDisables the field and actions while submitted or streaming.
allowSubmitWhileRunningbooleanfalseLet Enter and Send submit during a run.
allowEmptySubmitbooleanfalseAllow submitting with no text and no attachments.
maxHeightnumber | string240Field height cap. Numbers are pixels.
classNamestring-Tailwind classes merged via cn().
childrenReact.ReactNode-Queue, shell and footer.

Variants

VariantOptionsDefaultDescription
layout"stacked" "inline" "compact""stacked"Toolbar placement and the single-row starting shape.
variant"primary" "secondary""primary"Surface color of the shell and the queue card.
size"xs" "sm" "md""sm"Type scale, minimum field height and icon button size.

PromptInput.Shell

The bordered surface and focus ring. Clicking empty space inside it focuses the field. Extends React.ComponentProps<"div">.

Props

PropTypeDefaultDescription
classNamestring-Tailwind classes merged via cn().
childrenReact.ReactNode-Content and Toolbar.

PromptInput.Content

The column holding attachments and the field. In the single-row layouts it uses display: contents. Extends React.ComponentProps<"div">.

Props

PropTypeDefaultDescription
classNamestring-Tailwind classes merged via cn().
childrenReact.ReactNode-Attachments and the field.

PromptInput.Attachments

The attachment row. Reports whether it has element children, which expands single-row layouts and allows sending without text. Extends React.ComponentProps<"div">.

Props

PropTypeDefaultDescription
classNamestring-Tailwind classes merged via cn().
childrenReact.ReactNode-ChatAttachment tiles.

PromptInput.TextArea

The autosizing field. Extends React.ComponentProps<"textarea">; value, onChange and disabled are wired to the root. Your onKeyDown runs before the built-in Enter handling.

Props

PropTypeDefaultDescription
disableAutosizebooleanfalseKeep a fixed height.
placeholderstring"What do you want to know?"Placeholder text.
aria-labelstring"Message"Accessible name.
classNamestring-Tailwind classes merged via cn().

PromptInput.Toolbar

The action row. Floats at the bottom of a stacked shell and uses display: contents in single-row layouts. ToolbarStart and ToolbarEnd are its leading and trailing groups. All three extend React.ComponentProps<"div">.

Props

PropTypeDefaultDescription
classNamestring-Tailwind classes merged via cn().
childrenReact.ReactNode-Actions and groups.

PromptInput.Action

An icon-only toolbar button. Extends the Button props and disables itself with the field.

Props

PropTypeDefaultDescription
tooltipReact.ReactNode-Wraps the button in a Tooltip.
variantButtonProps["variant"]"neutral-light"Button color.
appearanceButtonProps["appearance"]"ghost"Button fill.
sizeButtonProps["size"]composer sizeOverrides the size derived from the root.
aria-labelstring-Required. Names the icon.
classNamestring-Tailwind classes merged via cn().
childrenReact.ReactNode-The icon.

PromptInput.Send

The send, stop and retry button. Extends the Button props. disabled can only add to the built-in rules (nothing to send, running without onStop).

Props

PropTypeDefaultDescription
status"ready" | "submitted" | "streaming" | "error"root statusOverrides the status for this button only.
onStop() => voidroot onStopOverrides the stop handler for this button.
sendLabelstring"Send message"Accessible name while it sends.
stopLabelstring"Stop generating"Accessible name while it stops a run.
retryLabelstring"Retry"Accessible name in the error status.
disabledboolean-Forces the button off.
classNamestring-Tailwind classes merged via cn().
childrenReact.ReactNodestatus iconReplaces the icon.

PromptInput.Footer

A centered disclaimer under the composer. Extends React.ComponentProps<"p">.

Props

PropTypeDefaultDescription
classNamestring-Tailwind classes merged via cn().
childrenReact.ReactNode-The disclaimer text.

usePromptInput / usePromptInputValue

Hooks for custom parts inside PromptInput. usePromptInput() returns setValue, submit, stop, status, isRunning, isDisabled, effectiveDisabled, size, variant, layout, mode, hasAttachments and the field refs, and does not re-render on typing. usePromptInputValue() returns value, hasContent and canSubmit, and re-renders on every keystroke.

PromptInputQueue

The queue card, from prompt-input-queue. Must sit inside PromptInput. Extends React.ComponentProps<"div">. Also exported as PromptInputQueueRoot.

Props

PropTypeDefaultDescription
actionsVisibility"always" | "hover""hover"Pin row actions, or reveal them on hover and focus.
classNamestring-Tailwind classes merged via cn().
childrenReact.ReactNode-A List.

PromptInputQueue.List

The scrollable <ul>. With values and onReorder it renders a Motion Reorder.Group. Extends React.ComponentProps<"ul"> without the Motion-conflicting drag and animation handlers.

Props

PropTypeDefaultDescription
valuesT[]-Items in order. Needed for reordering.
onReorder(values: T[]) => void-Called with the new order after a drag or key move.
axis"x" | "y""y"Reorder direction.
getMoveAnnouncement(position: number, total: number, value: T) => string-Live region text after a keyboard move.
classNamestring-Tailwind classes merged on the scroll container.
childrenReact.ReactNode-Item rows.

PromptInputQueue.Item

One row, an <li>. Extends React.ComponentProps<"li"> without value and the Motion-conflicting handlers.

Props

PropTypeDefaultDescription
valueT-The matching entry from values. Needed to drag.
classNamestring-Tailwind classes merged via cn().
childrenReact.ReactNode-Row parts.

PromptInputQueue.Item.Handle

The drag handle. Starts a pointer drag and moves the row with the arrow keys. Disabled when the list is not reorderable or has one row. Extends the Button props.

Props

PropTypeDefaultDescription
aria-labelstring"Reorder queued prompt"Accessible name.
classNamestring-Tailwind classes merged via cn().
childrenReact.ReactNodedrag iconReplaces the icon.

PromptInputQueue.Item.Body / Icon / Content / Description

Layout parts of a row. Body is a <div> that stacks when it contains Attachments. Icon is an aria-hidden <span> with a document icon by default. Content is a two-line clamped <p> and Description a secondary <p>. Each extends the props of its element and accepts className and children.

PromptInputQueue.Item.Attachments / AttachmentsOverflow

Attachments is a <div> for small ChatAttachment tiles; placed directly on the row it wraps onto its own line. AttachmentsOverflow is a <span> that renders nothing unless hiddenCount is above zero.

Props

PropTypeDefaultDescription
hiddenCountnumber-AttachmentsOverflow. Number of hidden files.
nounstring"files"AttachmentsOverflow. Noun in +N files.
classNamestring-Tailwind classes merged via cn().
childrenReact.ReactNode-Tiles, or a custom overflow label.

PromptInputQueue.Item.Actions / Action / Remove / More

Actions is the <div> that follows actionsVisibility. Action is a ghost Button, icon-only when it has no children. Remove and More are Actions with a trash and a more icon, labelled "Remove from queue" and "More queue actions". All buttons extend the Button props and disable with the composer.

PromptInputTokens

The token wrapper, from prompt-input-tokens. Wraps React Aria's Autocomplete and renders no DOM. Also exported as PromptInputTokensRoot.

Props

PropTypeDefaultDescription
childrenReact.ReactNode-PromptInput.Content and Suggestions.

PromptInputTokens.Input

The token field, a replacement for PromptInput.TextArea. Wraps React Aria's TokenField and TokenInput and writes its plain text back to the root. Also exported as PromptInputTokenInput.

Props

PropTypeDefaultDescription
tokensPromptInputTokenState-Required. The state from usePromptInputTokens.
placeholderstring"What do you want to know?"Placeholder text.
aria-labelstring"Message"Accessible name.
aria-labelledbystring-Labelling element id. Wins over aria-label.
allowsNewlinesbooleantrueLet Shift + Enter insert a newline.
isReadOnlybooleanfalseMake the field read only.
isDisabledbooleancomposer stateOverrides the disabled and run-lock state.
classNamestring-Tailwind classes merged via cn().
children(segment: TokenSegment) => React.ReactElementrenders TokenCustom token rendering.

PromptInputTokens.Token

An inline token chip. Wraps React Aria's Token. Also exported as PromptInputToken.

Props

PropTypeDefaultDescription
classNamestring-Tailwind classes merged via cn().
childrenReact.ReactNode-The token text.

PromptInputTokens.Suggestions

A non-modal popover anchored to the active trigger, drawn with the DropdownPopover surface. Extends React Aria's Popover props without triggerRef and getTargetRect. Also exported as PromptInputTokenSuggestions.

Props

PropTypeDefaultDescription
tokensPromptInputTokenState-Required. The state from usePromptInputTokens.
isOpenbooleantokens.isOpenOverrides the open state.
placementPlacement"bottom start"Position relative to the trigger.
classNamestring-Tailwind classes merged via cn().
childrenReact.ReactNode-A DropdownMenu.

usePromptInputTokens

Creates the token state. Also re-exports React Aria's TokenFieldValue.

Props

PropTypeDefaultDescription
triggersstring[]["@", "/"]Strings that open the menu at a word start.
allowsSpacesbooleanfalseLet the query continue past a space.
defaultValueTokenFieldValueemptyInitial value when uncontrolled.
valueTokenFieldValue-Controlled value.
onChange(value: TokenFieldValue) => void-Called on every change.

It returns value, setValue, clear, inputRef, anchor, query, trigger, isOpen, insertToken(text, { value, appendSpace }) and close.