# Composer

> The message box at the bottom of a conversation, with the input, its tools, attachments, @ suggestions and the pink send button inside one ink border.

Source: https://raft-design.botiverse.build/components/composer. Generated from the current release; where it differs from what you remember, this page is right.

The composer is where people write a message: at the bottom of a channel, a direct message, a thread or a task's thread. One ink border holds the input, the tools, any attachments and the pink send button. Typing @ opens a list of people and agents above it.

```tsx
import { ImagePlus, Paperclip, Send } from "lucide-react";
import { useCallback, useState } from "react";
import type { ChangeEvent, FormEvent } from "react";
import {
  Checkbox,
  ComposerActions,
  ComposerIconButton,
  ComposerMeta,
  ComposerRoot,
  ComposerSubmit,
  ComposerToolbarLabel,
} from "@botiverse/raft-ui";
import { Composer, ComposerInput, ComposerToolbar } from "@botiverse/raft-ui";

export function ComposerDefaultComposition() {
  const [draft, setDraft] = useState("");
  const [hasAttachment, setHasAttachment] = useState(false);

  const handleSubmit = useCallback(
    (event: FormEvent<HTMLFormElement>) => {
      event.preventDefault();
      if (!draft.trim() && !hasAttachment) return;
      setDraft("");
      setHasAttachment(false);
    },
    [draft, hasAttachment],
  );

  const handleDraftChange = useCallback((event: ChangeEvent<HTMLTextAreaElement>) => {
    setDraft(event.currentTarget.value);
  }, []);

  const handleAttach = useCallback(() => {
    setHasAttachment(true);
  }, []);

  return (
    <ComposerRoot onSubmit={handleSubmit}>
      <Composer>
        <ComposerInput
          aria-label="Message #proj-uiux"
          placeholder="Message #proj-uiux"
          value={draft}
          onChange={handleDraftChange}
        />
        <ComposerToolbar>
          <ComposerActions>
            <ComposerIconButton aria-label="Attach image" onClick={handleAttach}>
              <ImagePlus />
            </ComposerIconButton>
            <ComposerIconButton aria-label="Attach file" onClick={handleAttach}>
              <Paperclip />
            </ComposerIconButton>
          </ComposerActions>
          <ComposerMeta>
            <ComposerToolbarLabel>
              <Checkbox aria-label="Send as task" />
              As Task
            </ComposerToolbarLabel>
            <ComposerSubmit aria-label="Send" disabled={!draft.trim() && !hasAttachment}>
              <span>Send</span>
              <Send />
            </ComposerSubmit>
          </ComposerMeta>
        </ComposerToolbar>
      </Composer>
    </ComposerRoot>
  );
}
```

## When to use

| Situation                               | Use                                 |
| --------------------------------------- | ----------------------------------- |
| Sending a message in a conversation     | Composer                            |
| Several lines of text saved with a form | [Textarea](https://raft-design.botiverse.build/components/textarea.md) |
| One line of text                        | [Input](https://raft-design.botiverse.build/components/input.md)          |

Where the composer sits in a conversation is in [Conversation](https://raft-design.botiverse.build/patterns/conversation.md).

## Anatomy

- `ComposerRoot`: the `<form>` that submits the message. Required.
- `Composer`: the box with the ink border. Required.
- `ComposerAttachments` with one `ComposerAttachment` per file: 64px thumbnails inside the box, above the input. Optional. `SortableComposerAttachments` lets people reorder them.
- `ComposerInput`: the input, a `<textarea>` that grows with its content. Required.
- `ComposerToolbar`: the row under the input, with:
  - `ComposerActions`: the tools at the start, each a `ComposerIconButton` (attach a file, add an image);
  - `ComposerMeta`: at the end, an option attached to sending (`ComposerToolbarLabel` with a [Checkbox](https://raft-design.botiverse.build/components/checkbox.md)) and `ComposerSubmit`, the send button.
- `ComposerSuggestionList` and its parts, driven by `useComposerSuggestions`: the @ suggestions above the box.
- `ComposerDropOverlay`: what the box shows while files are dragged over it.
- @ delegation chips (desktop): a row of agents that puts an @ mention into the input when one is clicked. raft-ui has no part for it yet.

## States

```tsx
import { ImagePlus, Paperclip, Send, X } from "lucide-react";
import {
  ComposerActions,
  ComposerAttachment,
  ComposerAttachmentBody,
  ComposerAttachmentFailedOverlay,
  ComposerAttachmentFile,
  ComposerAttachmentImage,
  ComposerAttachmentMeta,
  ComposerAttachmentRemove,
  ComposerAttachments,
  ComposerAttachmentTitle,
  ComposerAttachmentUploadingOverlay,
  ComposerAttachmentUploadProgressBar,
  ComposerIconButton,
  ComposerMeta,
  ComposerRoot,
  ComposerSubmit,
  FilePreview,
  FilePreviewBadge,
  FilePreviewDocument,
  FilePreviewMedia,
  Spinner,
} from "@botiverse/raft-ui";
import { Composer, ComposerInput, ComposerToolbar } from "@botiverse/raft-ui";

import { COMPOSER_IMAGE_SRC } from "../composer-image.ts";

export function ComposerAttachmentStatesComposition() {
  return (
    <ComposerRoot>
      <ComposerAttachments>
        <ComposerAttachment>
          <ComposerAttachmentImage>
            <FilePreview>
              <FilePreviewMedia>
                <img alt="" className="size-full object-cover" src={COMPOSER_IMAGE_SRC} />
              </FilePreviewMedia>
            </FilePreview>
          </ComposerAttachmentImage>
          <ComposerAttachmentUploadingOverlay>
            <Spinner size="sm" className="mb-1" />
            64%
          </ComposerAttachmentUploadingOverlay>
          <ComposerAttachmentUploadProgressBar value={64} />
          <ComposerAttachmentRemove aria-label="Remove uploading-screenshot.png" disabled>
            <X />
          </ComposerAttachmentRemove>
        </ComposerAttachment>

        <ComposerAttachment>
          <ComposerAttachmentFile>
            <FilePreview className="h-[46px]">
              <FilePreviewDocument className="h-[46px]" />
              <FilePreviewBadge>PDF</FilePreviewBadge>
            </FilePreview>
            <ComposerAttachmentBody>
              <ComposerAttachmentTitle>release-notes.pdf</ComposerAttachmentTitle>
              <ComposerAttachmentMeta>18 KB</ComposerAttachmentMeta>
            </ComposerAttachmentBody>
          </ComposerAttachmentFile>
          <ComposerAttachmentRemove aria-label="Remove release-notes.pdf" disabled>
            <X />
          </ComposerAttachmentRemove>
        </ComposerAttachment>

        <ComposerAttachment>
          <ComposerAttachmentFile>
            <FilePreview className="h-[46px]">
              <FilePreviewDocument className="h-[46px]" />
              <FilePreviewBadge>PDF</FilePreviewBadge>
            </FilePreview>
            <ComposerAttachmentBody>
              <ComposerAttachmentTitle>failed-upload.pdf</ComposerAttachmentTitle>
              <ComposerAttachmentMeta>18 KB</ComposerAttachmentMeta>
            </ComposerAttachmentBody>
          </ComposerAttachmentFile>
          <ComposerAttachmentFailedOverlay render={<button type="button" disabled />}>
            <span>Upload failed</span>
            <span className="underline underline-offset-2">Retry</span>
          </ComposerAttachmentFailedOverlay>
          <ComposerAttachmentRemove aria-label="Remove failed-upload.pdf" disabled>
            <X />
          </ComposerAttachmentRemove>
        </ComposerAttachment>
      </ComposerAttachments>
      <Composer>
        <ComposerInput aria-label="Message #proj-uiux" placeholder="Message #proj-uiux" readOnly />
        <ComposerToolbar>
          <ComposerActions>
            <ComposerIconButton aria-label="Attach image" disabled>
              <ImagePlus />
            </ComposerIconButton>
            <ComposerIconButton aria-label="Attach file" disabled>
              <Paperclip />
            </ComposerIconButton>
          </ComposerActions>
          <ComposerMeta>
            <ComposerSubmit aria-label="Send" disabled>
              <Send />
            </ComposerSubmit>
          </ComposerMeta>
        </ComposerToolbar>
      </Composer>
    </ComposerRoot>
  );
}
```

- **Uploading:** a thumbnail shows cyan progress on its own track, inset from its edges ([Shape](https://raft-design.botiverse.build/foundations/shape.md#nothing-on-the-edge-of-a-rounded-container)); × in its corner cancels the upload while it runs and removes the file after. File cards are in [MessageAttachment](https://raft-design.botiverse.build/components/message-attachment.md).
- **Nothing to send:** disable `ComposerSubmit` until there is text or an attachment.
- **Focus:** the whole composer takes the focus ring ([States](https://raft-design.botiverse.build/foundations/states.md#focus)).

## Behavior

```tsx
import { ImagePlus, Paperclip, Send } from "lucide-react";
import { useCallback, useState } from "react";
import type { FormEvent } from "react";
import {
  Checkbox,
  ComposerActions,
  ComposerIconButton,
  ComposerMeta,
  ComposerRoot,
  ComposerSubmit,
  ComposerToolbarLabel,
  useComposerSuggestions,
} from "@botiverse/raft-ui";
import { Composer, ComposerInput, ComposerToolbar } from "@botiverse/raft-ui";

import {
  detectSuggestion,
  getSuggestionText,
  getSuggestions,
  isSuggestionDisabled,
} from "./composer-suggestions.data.ts";
import type { Suggestion, SuggestionDataset } from "./composer-suggestions.data.ts";
import { ComposerSuggestionResults } from "./composer-suggestion-results.tsx";

export type ComposerSuggestionCompositionProps = {
  dataset?: SuggestionDataset;
  initialValue?: string;
};

export function ComposerSuggestionComposition({
  dataset = "default",
  initialValue = "@",
}: ComposerSuggestionCompositionProps) {
  const [value, setValue] = useState(initialValue);
  const [alsoCreateTask, setAlsoCreateTask] = useState(false);
  const [statusMessage, setStatusMessage] = useState("");
  const autocomplete = useComposerSuggestions<Suggestion>({
    value,
    onValueChange: setValue,
    detectTrigger: detectSuggestion,
    getItems: (match) => getSuggestions(match, dataset),
    getItemText: getSuggestionText,
    isItemDisabled: isSuggestionDisabled,
  });
  const handleSubmit = useCallback(
    (event: FormEvent<HTMLFormElement>) => {
      event.preventDefault();
      if (!value.trim()) return;
      setStatusMessage(`Sent${alsoCreateTask ? " as task" : ""}: ${value.trim()}`);
      autocomplete.clear();
    },
    [alsoCreateTask, autocomplete, value],
  );
  const handleAttachImage = useCallback(() => {
    setStatusMessage("Image attachment selected");
  }, []);
  const handleAttachFile = useCallback(() => {
    setStatusMessage("File attachment selected");
  }, []);
  return (
    <div className="flex h-full min-w-0 flex-col justify-end p-3">
      <ComposerRoot onSubmit={handleSubmit}>
        {autocomplete.open ? (
          <ComposerSuggestionResults
            items={autocomplete.items}
            getListProps={autocomplete.getListProps}
            getOptionProps={autocomplete.getOptionProps}
          />
        ) : null}
        <Composer>
          <ComposerInput
            {...autocomplete.getInputProps({
              "aria-label": "Message #proj-uiux",
              placeholder: "Type @ for people or # for channels",
            })}
          />
          <ComposerToolbar>
            <ComposerActions>
              <ComposerIconButton aria-label="Attach image" onClick={handleAttachImage}>
                <ImagePlus />
              </ComposerIconButton>
              <ComposerIconButton aria-label="Attach file" onClick={handleAttachFile}>
                <Paperclip />
              </ComposerIconButton>
            </ComposerActions>
            <ComposerMeta>
              <ComposerToolbarLabel>
                <Checkbox
                  aria-label="Send as task"
                  checked={alsoCreateTask}
                  onCheckedChange={setAlsoCreateTask}
                />
                As Task
              </ComposerToolbarLabel>
              <ComposerSubmit aria-label="Send" disabled={!value.trim()}>
                <span>Send</span>
                <Send />
              </ComposerSubmit>
            </ComposerMeta>
          </ComposerToolbar>
        </Composer>
        <span className="sr-only" aria-live="polite">
          {statusMessage}
        </span>
      </ComposerRoot>
    </div>
  );
}
```

- Typing @ opens the suggestion list above the composer, as wide as the composer and 8px above it. Group it with members of this conversation first, then the others. Each row shows the avatar, the name and the status in `foreground-hint`.
- Let people drag files into the composer, but not folders.
- Grow the input with its content up to a maximum height, then scroll inside it.
- On a phone, keep the composer on top of the keyboard when the keyboard opens. Return inserts a new line on a phone held upright ([Responsive layout](https://raft-design.botiverse.build/patterns/responsive.md#phone)), so sending uses the send button.
- Put the composer's border on the content margin ([Page structure](https://raft-design.botiverse.build/patterns/page-structure.md#one-edge-per-column)), with 16px plus the safe area below it.

```tsx
import { ImagePlus, Paperclip, Send } from "lucide-react";
import {
  ComposerDropOverlay,
  ComposerDropOverlayPrompt,
  ComposerActions,
  ComposerIconButton,
  ComposerMeta,
  ComposerRoot,
  ComposerSubmit,
} from "@botiverse/raft-ui";
import { Composer, ComposerInput, ComposerToolbar } from "@botiverse/raft-ui";

export function ComposerDropOverlayComposition() {
  return (
    <div className="relative w-full">
      <ComposerRoot>
        <Composer>
          <ComposerInput
            aria-label="Message #proj-uiux"
            placeholder="Message #proj-uiux"
            readOnly
          />
          <ComposerToolbar>
            <ComposerActions>
              <ComposerIconButton aria-label="Attach image" disabled>
                <ImagePlus />
              </ComposerIconButton>
              <ComposerIconButton aria-label="Attach file" disabled>
                <Paperclip />
              </ComposerIconButton>
            </ComposerActions>
            <ComposerMeta>
              <ComposerSubmit aria-label="Send" disabled>
                <Send />
              </ComposerSubmit>
            </ComposerMeta>
          </ComposerToolbar>
        </Composer>
      </ComposerRoot>
      <ComposerDropOverlay aria-hidden>
        <ComposerDropOverlayPrompt />
      </ComposerDropOverlay>
    </div>
  );
}
```

## Content

- Make the placeholder say where the message goes: Message #launch-week. Use the same words as the input's `aria-label`.

## Accessibility

Composer has no Base UI primitive: it renders a native `<form>` and `<textarea>`. `useComposerSuggestions` adds the combobox pattern to the input: `aria-controls` points at the listbox, `aria-activedescendant` at the highlighted option. Arrow Up and Arrow Down move the highlight, Enter or Tab picks it, and Esc closes the list.

- Give `ComposerInput` an `aria-label` that names the destination (Message #launch-week).
- Give every `ComposerIconButton` and an icon-only `ComposerSubmit` an `aria-label` (Attach File, Send).
- Give the suggestion list an `aria-label` or `aria-labelledby`.

## Rules

- Count the send button as the screen's pink button ([Button](https://raft-design.botiverse.build/components/button.md#which-color)). On a phone it is a pink icon button; on desktop it may carry a label (Send ↵).
- Draw one ink border around the whole group. The tools and chips inside it have none: `ComposerIconButton` is frameless, and delegation chips sit on `fill-muted`.
- Keep the input text `text-body` at regular weight wherever the composer sits (a channel, a thread, a comment on an attachment, a forward). Don't let it inherit the surrounding type. Make tool icons `foreground-muted`.
- Under a scrolling region, such as a comment on an attachment or a note added to a forward, use a compact composer: a `line-seam` above it, 12px of space around it, and no fill of its own.

## API

### Composer

| Prop | Type | Default | Description |
| --- | --- | --- | --- |
| `render` | `ReactElement \| ((props, state) => ReactElement)` |  | Allows you to replace the component's HTML element with a different tag, or compose it with another component. |

Also takes the attributes of its element.

### ComposerActions

| Prop | Type | Default | Description |
| --- | --- | --- | --- |
| `render` | `ReactElement \| ((props, state) => ReactElement)` |  | Allows you to replace the component's HTML element with a different tag, or compose it with another component. |

Also takes the attributes of its element.

### ComposerAttachment

| Prop | Type | Default | Description |
| --- | --- | --- | --- |
| `render` | `ReactElement \| ((props, state) => ReactElement)` |  | Allows you to replace the component's HTML element with a different tag, or compose it with another component. |

Also takes the attributes of its element.

### ComposerAttachmentBody

| Prop | Type | Default | Description |
| --- | --- | --- | --- |
| `render` | `ReactElement \| ((props, state) => ReactElement)` |  | Allows you to replace the component's HTML element with a different tag, or compose it with another component. |

Also takes the attributes of its element.

### ComposerAttachmentFailedOverlay

| Prop | Type | Default | Description |
| --- | --- | --- | --- |
| `render` | `ReactElement \| ((props, state) => ReactElement)` |  | Allows you to replace the component's HTML element with a different tag, or compose it with another component. |

Also takes the attributes of its element.

### ComposerAttachmentFile

| Prop | Type | Default | Description |
| --- | --- | --- | --- |
| `render` | `ReactElement \| ((props, state) => ReactElement)` |  | Allows you to replace the component's HTML element with a different tag, or compose it with another component. |

Also takes the attributes of its element.

### ComposerAttachmentImage

| Prop | Type | Default | Description |
| --- | --- | --- | --- |
| `render` | `ReactElement \| ((props, state) => ReactElement)` |  | Allows you to replace the component's HTML element with a different tag, or compose it with another component. |

Also takes the attributes of its element.

### ComposerAttachmentMeta

| Prop | Type | Default | Description |
| --- | --- | --- | --- |
| `render` | `ReactElement \| ((props, state) => ReactElement)` |  | Allows you to replace the component's HTML element with a different tag, or compose it with another component. |

Also takes the attributes of its element.

### ComposerAttachmentRemove

| Prop | Type | Default | Description |
| --- | --- | --- | --- |
| `render` | `ReactElement \| ((props, state) => ReactElement)` |  | Allows you to replace the component's HTML element with a different tag, or compose it with another component. |

Also takes the attributes of its element.

### ComposerAttachments

| Prop | Type | Default | Description |
| --- | --- | --- | --- |
| `render` | `ReactElement \| ((props, state) => ReactElement)` |  | Allows you to replace the component's HTML element with a different tag, or compose it with another component. |

Also takes the attributes of its element.

### ComposerAttachmentsScrollArea

Also takes the attributes of its element.

### ComposerAttachmentTitle

| Prop | Type | Default | Description |
| --- | --- | --- | --- |
| `render` | `ReactElement \| ((props, state) => ReactElement)` |  | Allows you to replace the component's HTML element with a different tag, or compose it with another component. |

Also takes the attributes of its element.

### ComposerAttachmentUploadingOverlay

| Prop | Type | Default | Description |
| --- | --- | --- | --- |
| `render` | `ReactElement \| ((props, state) => ReactElement)` |  | Allows you to replace the component's HTML element with a different tag, or compose it with another component. |

Also takes the attributes of its element.

### ComposerAttachmentUploadProgressBar

| Prop | Type | Default | Description |
| --- | --- | --- | --- |
| `value` (required) | `number` |  |  |
| `render` | `ReactElement \| ((props, state) => ReactElement)` |  | Allows you to replace the component's HTML element with a different tag, or compose it with another component. |

Also takes the attributes of its element.

### ComposerDropOverlay

| Prop | Type | Default | Description |
| --- | --- | --- | --- |
| `render` | `ReactElement \| ((props, state) => ReactElement)` |  | Allows you to replace the component's HTML element with a different tag, or compose it with another component. |

Also takes the attributes of its element.

### ComposerDropOverlayLabel

| Prop | Type | Default | Description |
| --- | --- | --- | --- |
| `render` | `ReactElement \| ((props, state) => ReactElement)` |  | Allows you to replace the component's HTML element with a different tag, or compose it with another component. |

Also takes the attributes of its element.

### ComposerDropOverlayPrompt

| Prop | Type | Default | Description |
| --- | --- | --- | --- |
| `render` | `ReactElement \| ((props, state) => ReactElement)` |  | Allows you to replace the component's HTML element with a different tag, or compose it with another component. |

Also takes the attributes of its element.

### ComposerIconButton

| Prop | Type | Default | Description |
| --- | --- | --- | --- |
| `loading` | `boolean` |  |  |
| `loadingLabel` | `string` |  |  |
| `focusableWhenDisabled` | `boolean` | `false` | Whether the button should be focusable when disabled. |
| `nativeButton` | `boolean` | `true` | Whether the component renders a native `<button>` element when replacing it via the `render` prop. Set to `false` if the rendered element is not a button (for example, `<div>`). |
| `render` | `ReactElement \| ((props, state) => ReactElement)` |  | Allows you to replace the component's HTML element with a different tag, or compose it with another component. |

Also takes the attributes of its element.

### ComposerInput

| Prop | Type | Default | Description |
| --- | --- | --- | --- |
| `render` | `ReactElement \| ((props, state) => ReactElement)` |  | Allows you to replace the component's HTML element with a different tag, or compose it with another component. |

Also takes the attributes of its element.

### ComposerMeta

| Prop | Type | Default | Description |
| --- | --- | --- | --- |
| `render` | `ReactElement \| ((props, state) => ReactElement)` |  | Allows you to replace the component's HTML element with a different tag, or compose it with another component. |

Also takes the attributes of its element.

### ComposerRoot

| Prop | Type | Default | Description |
| --- | --- | --- | --- |
| `render` | `ReactElement \| ((props, state) => ReactElement)` |  | Allows you to replace the component's HTML element with a different tag, or compose it with another component. |

Also takes the attributes of its element.

### ComposerSubmit

| Prop | Type | Default | Description |
| --- | --- | --- | --- |
| `loading` | `boolean` |  |  |
| `loadingLabel` | `string` |  |  |
| `size` | `"xs" \| "sm" \| "md" \| "lg" \| "xl" \| "inline" \| "icon-xs" \| "icon-sm" \| "icon-md" \| "icon-lg"` |  |  |
| `focusableWhenDisabled` | `boolean` | `false` | Whether the button should be focusable when disabled. |
| `nativeButton` | `boolean` | `true` | Whether the component renders a native `<button>` element when replacing it via the `render` prop. Set to `false` if the rendered element is not a button (for example, `<div>`). |
| `render` | `ReactElement \| ((props, state) => ReactElement)` |  | Allows you to replace the component's HTML element with a different tag, or compose it with another component. |

Also takes the attributes of its element.

### ComposerSuggestionAside

| Prop | Type | Default | Description |
| --- | --- | --- | --- |
| `render` | `ReactElement \| ((props, state) => ReactElement)` |  | Allows you to replace the component's HTML element with a different tag, or compose it with another component. |

Also takes the attributes of its element.

### ComposerSuggestionContent

| Prop | Type | Default | Description |
| --- | --- | --- | --- |
| `render` | `ReactElement \| ((props, state) => ReactElement)` |  | Allows you to replace the component's HTML element with a different tag, or compose it with another component. |

Also takes the attributes of its element.

### ComposerSuggestionGroup

| Prop | Type | Default | Description |
| --- | --- | --- | --- |
| `separated` | `boolean` | `false` |  |
| `render` | `ReactElement \| ((props, state) => ReactElement)` |  | Allows you to replace the component's HTML element with a different tag, or compose it with another component. |

Also takes the attributes of its element.

### ComposerSuggestionGroupLabel

| Prop | Type | Default | Description |
| --- | --- | --- | --- |
| `render` | `ReactElement \| ((props, state) => ReactElement)` |  | Allows you to replace the component's HTML element with a different tag, or compose it with another component. |

Also takes the attributes of its element.

### ComposerSuggestionIcon

| Prop | Type | Default | Description |
| --- | --- | --- | --- |
| `variant` | `"inline" \| "avatar" \| "framed" \| "auxiliary"` | `"avatar"` |  |
| `render` | `ReactElement \| ((props, state) => ReactElement)` |  | Allows you to replace the component's HTML element with a different tag, or compose it with another component. |

Also takes the attributes of its element.

### ComposerSuggestionList

| Prop | Type | Default | Description |
| --- | --- | --- | --- |
| `render` | `ReactElement \| ((props, state) => ReactElement)` |  | Allows you to replace the component's HTML element with a different tag, or compose it with another component. |

Also takes the attributes of its element.

### ComposerSuggestionMeta

| Prop | Type | Default | Description |
| --- | --- | --- | --- |
| `variant` | `"code" \| "text"` | `"text"` |  |
| `render` | `ReactElement \| ((props, state) => ReactElement)` |  | Allows you to replace the component's HTML element with a different tag, or compose it with another component. |

Also takes the attributes of its element.

### ComposerSuggestionOption

| Prop | Type | Default | Description |
| --- | --- | --- | --- |
| `render` | `ReactElement \| ((props, state) => ReactElement)` |  | Allows you to replace the component's HTML element with a different tag, or compose it with another component. |

Also takes the attributes of its element.

### ComposerSuggestionTitle

| Prop | Type | Default | Description |
| --- | --- | --- | --- |
| `render` | `ReactElement \| ((props, state) => ReactElement)` |  | Allows you to replace the component's HTML element with a different tag, or compose it with another component. |

Also takes the attributes of its element.

### ComposerToolbar

| Prop | Type | Default | Description |
| --- | --- | --- | --- |
| `render` | `ReactElement \| ((props, state) => ReactElement)` |  | Allows you to replace the component's HTML element with a different tag, or compose it with another component. |

Also takes the attributes of its element.

### ComposerToolbarLabel

| Prop | Type | Default | Description |
| --- | --- | --- | --- |
| `render` | `ReactElement \| ((props, state) => ReactElement)` |  | Allows you to replace the component's HTML element with a different tag, or compose it with another component. |

Also takes the attributes of its element.

### SortableComposerAttachment

Sized placeholder stays in the flex flow (opacity 0). DragOverlay is the pointer ghost — the root reads these `children` for it. Live draft reorder moves this slot to the drop index; wrap skips strategy transforms so siblings FLIP via layout instead of wrong gaps.

| Prop | Type | Default | Description |
| --- | --- | --- | --- |
| `children` (required) | `ReactNode` |  |  |
| `id` (required) | `string` |  |  |
| `label` (required) | `string` |  |  |

### SortableComposerAttachments

Wrap / flex-pack attachment reorder (mixed widths).

| Prop | Type | Default | Description |
| --- | --- | --- | --- |
| `children` (required) | `ReactNode` |  |  |
| `items` (required) | `readonly string[]` |  |  |
| `onReorder` (required) | `(items: string[]) => void` |  |  |

### SortableComposerAttachmentsScroll

Horizontal scroll strip reorder — wraps `ComposerAttachmentsScrollArea`.

| Prop | Type | Default | Description |
| --- | --- | --- | --- |
| `children` (required) | `ReactNode` |  |  |
| `items` (required) | `readonly string[]` |  |  |
| `onReorder` (required) | `(items: string[]) => void` |  |  |

## Changes

- 0.1.0 (Added): Graduated `AppRail`, `AppShell` (with sidebar collapse trigger), `ChatSidebar`, `Composer` (including sortable attachments), and `Resizable*` to the stable entry.
