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 composerFull 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.
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
| Key | Action |
|---|---|
Enter | Submits the composer (calls onSubmit) |
Shift + Enter | Inserts 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 composerThis copies two files into your project:
components/ui/composer/composer.tsx— the componentcomponents/ui/composer/composer.module.css— the styles
API Reference
ComposerProps
| Prop | Type | Default | Description |
|---|---|---|---|
value | string | — | Controlled value of the composer field. |
onValueChange | (value: string) => void | — | Called when the field value changes (controlled mode). |
onSubmit | (value: string) => void | — | Called when the field is submitted — Enter key or the send button. Shift+Enter inserts a newline instead. |
disabled | boolean | false | Disables all interactive children (field, tool buttons, send). |
placeholder | string | 'Message…' | ComposerField: placeholder text. |
icon* | ReactNode | — | ComposerToolButton: Phosphor icon element rendered inside the button. |
aria-label* | string | — | ComposerToolButton: accessible label (required — the button is icon-only). |
className | string | — | Additional CSS class names to merge onto any sub-component. |
The root also accepts all standard <div> HTML attributes.
ComposerFieldProps
| Prop | Type | Default | Description |
|---|---|---|---|
placeholder | string | "Message…" | Placeholder text shown when the field is empty. |
disabled | boolean | — | Overrides 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
| Prop | Type | Default | Description |
|---|---|---|---|
icon | React.ReactNode | — | Phosphor icon element to render inside the 32px circular button. |
aria-label | string | — | Required accessible label. |
ComposerToolButton also accepts all standard <button> attributes.
ComposerSendProps
| Prop | Type | Default | Description |
|---|---|---|---|
aria-label | string | "Send" | Accessible label for the send button. |
disabled | boolean | — | Explicitly disable the button (otherwise auto-disabled when field is empty). |
ComposerSend also accepts all standard <button> attributes except type.