VisorVisor
ComponentsForm

Composer

AI-chat composer — a rounded card container with an auto-growing text field, a tools row for icon buttons and status chips, and a circular primary send button.

npx visor add composer

Full example

The composer composes a text field, tool buttons, an arbitrary status-chip slot, a spacer, and a send button inside a single rounded card container.

Claude · key active

Minimal

A bare composer with just a field and send button.

Disabled state

Pass disabled to the root to disable all interactive children simultaneously.

Controlled mode

Supply value and onValueChange to drive the field from external state. onSubmit fires when the user presses Enter or clicks the send button.

const [message, setMessage] = React.useState('');

<Composer
  value={message}
  onValueChange={setMessage}
  onSubmit={(value) => {
    sendMessage(value);
    setMessage('');
  }}
>
  <ComposerField placeholder="Message…" />
  <ComposerToolbar>
    <ComposerSpacer />
    <ComposerSend />
  </ComposerToolbar>
</Composer>

Keyboard interaction

KeyAction
EnterSubmits the composer (calls onSubmit)
Shift + EnterInserts a newline — does not submit

Status chip placement

The model/key status chip is not part of Composer — consumers compose Chip and StatusDot from the library and place them inside ComposerToolbar. This keeps the toolbar slot open for any content.

<ComposerToolbar>
  <ComposerToolButton icon={<Paperclip size={16} />} aria-label="Attach file" />
  {/* Chip + StatusDot composed by the consumer */}
  <Chip size="sm" leadingIcon={<StatusDot tone="mint" />} label="Claude · key active" />
  <ComposerSpacer />
  <ComposerSend />
</ComposerToolbar>

Installation

npx visor add composer

This copies two files into your project:

  • components/ui/composer/composer.tsx — the component
  • components/ui/composer/composer.module.css — the styles

API Reference

ComposerProps

PropTypeDefaultDescription
valuestringControlled value of the composer field.
onValueChange(value: string) => voidCalled when the field value changes (controlled mode).
onSubmit(value: string) => voidCalled when the field is submitted — Enter key or the send button. Shift+Enter inserts a newline instead.
disabledbooleanfalseDisables all interactive children (field, tool buttons, send).
placeholderstring'Message…'ComposerField: placeholder text.
icon*ReactNodeComposerToolButton: Phosphor icon element rendered inside the button.
aria-label*stringComposerToolButton: accessible label (required — the button is icon-only).
classNamestringAdditional CSS class names to merge onto any sub-component.

The root also accepts all standard <div> HTML attributes.

ComposerFieldProps

PropTypeDefaultDescription
placeholderstring"Message…"Placeholder text shown when the field is empty.
disabledbooleanOverrides the field's disabled state. Falls back to Composer's disabled.

ComposerField also accepts all standard <textarea> attributes except value and onChange (managed by Composer).

ComposerToolButtonProps

PropTypeDefaultDescription
iconReact.ReactNodePhosphor icon element to render inside the 32px circular button.
aria-labelstringRequired accessible label.

ComposerToolButton also accepts all standard <button> attributes.

ComposerSendProps

PropTypeDefaultDescription
aria-labelstring"Send"Accessible label for the send button.
disabledbooleanExplicitly disable the button (otherwise auto-disabled when field is empty).

ComposerSend also accepts all standard <button> attributes except type.