Composer

A chat-style prompt input that grows with content, paired with a toolbar and a submit button.

Anatomy

A root holds the empty and focus state. The input is a styled textarea that registers a submit handler with the root, so the same submit button drives whichever input is mounted. The toolbar sits below the input as a separate strip.

TSX

Keyboard

  • Enter submits when the input is non-empty.
  • Shift+Enter inserts a newline.
  • Tab moves focus out of the input as expected.

API

Reference for each part of the component, including its available props and behavior.

Composer

The root. Owns the empty and focus state, the disabled flag, and the registration channel for inputs. Renders a relative flex column so the toolbar sits below the input and any future floating UI can anchor to the root.

The root is engine-agnostic. It tracks empty and focus state and exposes a registration hook that any input can hand its submit handler to. The default ComposerInput registers a textarea-based handler. For mentions, commands, and chips, drop in ComposerRichInput from composer-rich instead.

Prop

ComposerInput

A styled textarea that registers a submit handler with the root. Autosizes between one row and maxRows lines. Sends on Enter without Shift, ignores Enter during IME composition so input methods can commit without firing submit.

The input runs in either mode. Leave value unset and the component manages its own state, optionally seeded by defaultValue, and clears itself after submit. Pass value together with onValueChange to hand state ownership to the parent. In that mode the parent decides when to clear, typically by setting value to an empty string after onSubmit.

Prop

ComposerQuote

A strip above the input that displays a chunk of text the user is responding to — a quoted message, a selected passage, an inline reply. Mount it conditionally based on consumer state. The part is purely visual and owns no state of its own, so wiring is a single piece of consumer state plus two handlers: one to set it, one to clear it.

TSX

Pairs naturally with Selection — wire a SelectionButton's onSelect to set the quoted state, and the dismiss to clear it. After submit, clear the state in the same onSubmit handler that ships the message, so the quote disappears with the cleared input.

Prop

ComposerQuoteIcon

The leading icon slot. Sized small and tinted muted to match the quote content. Pass any icon as children.

Prop

ComposerQuoteContent

The quoted text slot. Caps at three lines and hides the rest. When the content overflows the cap, the part exposes data-overflows and fades the truncated edge with a bottom mask gradient to signal that there is more. The full text stays in the DOM so copy, screen readers, and the submit handler all see the complete quote.

Prop

ComposerQuoteDismiss

The trailing close affordance. A plain unstyled button slot — pass an icon as children and your own onClick to clear the quoted state. The part does not own any close behavior of its own, so the consumer is in full control of when the quote leaves.

Prop

ComposerToolbar

The bottom strip below the input. A flex row with a top divider. Drop in any buttons or menus. Keep ComposerToolbarSpacer near the end to push the trailing controls to the right.

Prop

ComposerToolbarSpacer

A flex group with ml-auto. Wraps the trailing controls so they cluster to the right edge of the toolbar.

Prop

ComposerSubmit

A headless trigger wired to the registered input's submit action. Renders a plain <button> by default and exposes a render prop so the consumer supplies the real button. The submit applies disabled whenever the input is empty or the root is disabled, and surfaces data-empty for fine-grained styling. Children pass through to the rendered element so the icon stays outside the styling concern.

TSX

Pass disabled to layer extra conditions on top of the built-in guards, like a quota check or an unmet form requirement. The button only flips on when every guard agrees: input non-empty, root not disabled, and the consumer's disabled is falsy.

TSX

When even that isn't enough (a fully custom button shape, a different trigger element, etc.), reach for useComposer and build your own.

Prop

useComposer

Hook for reading root state and triggering submit from a custom part. Returns { isEmpty, isFocused, disabled, submit }. Use it when building a custom submit button or a status indicator.

TSX

The hook is also the escape hatch for a fully custom submit. When ComposerSubmit doesn't fit (you want a split button, a different element, or your own disabled logic without the built-in guards), wire your own trigger.

TSX

The same hook is what turns the submit button into a stop button while a model is replying. Chat SDKs like TanStack AI, Vercel AI, or your own client expose a status flag and an abort handle. Branch on the status: when the stream is in flight, render a stop button that calls the abort, otherwise render the send button wired to submit. The stop branch ignores the empty guard so the user can always bail out of a long response.

TSX

The Send Stop demo wires this against a mocked TanStack AI client so the playground works without a server.