# Composer

The field where a message is written, under a MessageList: attachments, a text field that grows with its text, your tools and a send button. Enter sends and Shift+Enter starts a new line. While a reply is on its way the send button can become a stop button.

**Dependencies:** npm: 1 direct, 4 total; registry: 3 direct, 3 total.

## Usage

### When to use

- Write and send a message under a conversation, with attachments and a few tools.
- Ask an assistant something, and stop its reply while it streams in.

### When not to use

- A form field that is submitted with other fields. Use Textarea in a Field.
- Search a list or the app. Use FilterBar or Command.
- A one-line value. Use Input.

## Examples

### Message

```tsx
<Composer label="Message" placeholder="Write a message" onSubmit={sendMessage} actions={<Button size="sm" variant="ghost" iconOnly aria-label="Attach a file">
  <IconPaperclip size={16} />
</Button>} className="max-w-md" />
```

### With attachments

Files show above the text while they upload; canSubmit lets the message go without text.

```tsx
<Composer canSubmit label="Message" placeholder="Add a message" onSubmit={sendMessage} attachments={<AttachmentPreview name="brief.pdf" type="application/pdf" fileSize={482000} onRemove={removeFile} />} actions={<Button size="sm" variant="ghost" iconOnly aria-label="Attach a file">
  <IconPaperclip size={16} />
</Button>} className="max-w-md" />
```

### Stop a reply

While the assistant answers, the send button becomes a stop button.

```tsx
<Composer pending label="Message" placeholder="Ask anything" onSubmit={sendMessage} onStop={stopReply} className="max-w-md" />
```

### Reply

Reply in MessageActions puts the message in replyTo; the bar sits above the text until it is sent or canceled.

```tsx
<Composer label="Message" placeholder="Write a reply" replyTo={sampleReply} onCancelReply={cancelReply} onSubmit={sendMessage} className="max-w-md" />
```

### Tell others you are typing

onTypingChange reports when typing starts and stops; send it over your realtime channel.

```tsx
<Composer label="Message" placeholder="Write a message" onTypingChange={sendTyping} onSubmit={sendMessage} className="max-w-md" />
```

## Do and don't

### Do

- Place the Composer under its MessageList, and keep focus in it after sending.
- Turn pending on while an assistant's reply streams in, and pass onStop so the reader can cancel it.
- Put only a few tools in actions, such as attach and a model or format picker.

### Don't

- Do not clear a controlled composer before onSubmit has the text; clear it in onSubmit.
- Do not use the placeholder as the name; label names the field.
- Do not add a second send button outside the field.

## Props

| Name | Type | Values | Default | Description |
| --- | --- | --- | --- | --- |
| `label` (required) | `string` |  |  | Names the text field for screen readers, such as Message. |
| `placeholder` | `string` |  |  | A hint inside the empty field, such as Write a message. |
| `value` | `string` |  |  | The text, for a controlled composer. Clear it yourself in onSubmit. |
| `defaultValue` | `string` |  |  | The text at first render, for an uncontrolled composer. It clears itself after sending. |
| `onValueChange` | `(value: string) => void` |  |  | Called with the text on every change. |
| `onSubmit` | `(value: string) => void` |  |  | Called with the text when the message is sent. |
| `canSubmit` | `boolean` |  |  | Whether the message can be sent. Default: the text is not blank. Pass true to send attachments without text. |
| `pending` | `boolean` |  | `false` | A reply is on its way: nothing can be sent, and with onStop the send button becomes a stop button. Typing still works. |
| `onStop` | `() => void` |  |  | Called by the stop button while pending, such as to cancel a reply that streams in. |
| `disabled` | `boolean` |  | `false` | Nothing can be typed or sent, such as in a conversation the reader has left. |
| `maxRows` | `number` |  | `8` | The text field grows with its text up to this many lines, then scrolls. |
| `submitOnEnter` | `boolean` |  | `true` | Enter sends and Shift+Enter starts a new line. With false, Enter starts a new line and only the button sends. |
| `actions` | `node` |  |  | Tools at the start of the toolbar, such as an attach button. Use ghost icon Buttons in size sm. |
| `attachments` | `node` |  |  | AttachmentPreview tiles above the text. |
| `replyTo` | `{ author: React.ReactNode; text: React.ReactNode }` |  |  | The message being answered, shown in a bar above the text and read with the text field. |
| `onCancelReply` | `() => void` |  |  | Shows a button in the reply bar that cancels the reply. |
| `onTypingChange` | `(typing: boolean) => void` |  |  | Called with true when the reader starts typing and false once they stop: after typingTimeout without a key, when the text is cleared or sent, when the field loses focus, and on unmount. Send it to the others, who show it with MessageList typingUsers. |
| `typingTimeout` | `number` |  | `3000` | How long without typing, in ms, before onTypingChange(false). |
| `name` | `string` |  |  | The name of the text field in a submitted form. |
| `autoFocus` | `boolean` |  | `false` | Focuses the text field on mount, such as when a conversation opens. |
| `labels` | `{ send?: string; stop?: string; replyingTo?: string; cancelReply?: string }` |  |  | Button names and the words before a reply. Defaults: Send, Stop, Replying to, Cancel reply. Vietnamese: Gửi, Dừng, Đang trả lời, Hủy trả lời. |

## Anatomy

| Part | Description |
| --- | --- |
| `root` | The form, drawn as Textarea's raised field box: attachments, text and toolbar stacked. |
| `reply` | The bar of the message being answered, first in the box: a reply icon, the author and one line of the message, and a cancel button. |
| `attachments` | Optional row of AttachmentPreview tiles above the text. It wraps. |
| `input` | The text field. One line at first; it grows with its text up to maxRows lines, then scrolls. |
| `placeholder` | The hint in the empty field, in secondary text. |
| `toolbar` | The row under the text: your tools at the start, the send or stop button at the end. |
| `actions` | Your tools, such as an attach button. Ghost icon Buttons in size sm. |
| `send` | The solid icon Button in size sm that sends, or the secondary stop button while pending. |

## Accessibility

Role: `textbox`

| Keys | Action |
| --- | --- |
| `Enter` | Sends the message, unless submitOnEnter is false. Enter that confirms an input method's text never sends. |
| `Shift + Enter` | Starts a new line. |
| `Tab` | Moves from the text field to the attachments, the tools and the send button. |

- A form with a multi-line text field named by label (EN `Message`, VI `Tin nhắn`). The placeholder is a hint, not the name.
- The send button is named by labels.send (EN `Send`, VI `Gửi`). With nothing to send it is aria-disabled: it keeps its place in the tab order and does nothing.
- While pending with onStop, the send button becomes a stop button named by labels.stop (EN `Stop`, VI `Dừng`).
- Sending keeps focus in the text field, so the next message can be typed at once.
- The field box shows the focus halo only while the text field has focus; a focused tool keeps just its own ring.
- On a phone the Enter key shows Send (enterkeyhint).
- While replying, the text field is described by the reply bar, so a screen reader hears EN `Replying to Linh Tran` with the message.
- onTypingChange fires once when typing starts and once when it stops, never on every key, so the events it sends stay few.

## Install

```bash
npx werewolf-ui add composer
```

Run `npx werewolf-ui init` once in your app before adding components. Init sets up tokens, their CSS import and agent rules. Add installs this component and its dependencies while preserving your theme. See [Installation](https://www.werewolf-ui.dev/docs/installation.md) for requirements and the alternative registry setup.
