Raft Design System

Components · Inputs

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.

Stable
import { Composer } from "@botiverse/raft-ui";

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.

chat-composer/compositions/composer-default

When to use

SituationUse
Sending a message in a conversationComposer
Several lines of text saved with a formTextarea
One line of textInput

Where the composer sits in a conversation is in Conversation.

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) 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

chat-composer/compositions/composer-attachment-states
  • Uploading: a thumbnail shows cyan progress on its own track, inset from its edges (Shape); × in its corner cancels the upload while it runs and removes the file after. File cards are in MessageAttachment.
  • Nothing to send: disable ComposerSubmit until there is text or an attachment.
  • Focus: the whole composer takes the focus ring (States).

Behavior

composer-suggestions/composer-suggestions
  • 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), so sending uses the send button.
  • Put the composer's border on the content margin (Page structure), with 16px plus the safe area below it.
chat-composer/compositions/composer-drop-overlay

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). 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, ComposerActions, ComposerAttachment, ComposerAttachmentBody, ComposerAttachmentFailedOverlay, ComposerAttachmentFile, ComposerAttachmentImage, ComposerAttachmentMeta, ComposerAttachmentRemove, ComposerAttachments

PropTypeDefault
renderAllows you to replace the component's HTML element with a different tag, or compose it with another component.ReactElement | ((props, state) => ReactElement)

Also takes the attributes of its element.

ComposerAttachmentsScrollArea

Takes the attributes of its element.

ComposerAttachmentTitle, ComposerAttachmentUploadingOverlay

PropTypeDefault
renderAllows you to replace the component's HTML element with a different tag, or compose it with another component.ReactElement | ((props, state) => ReactElement)

Also takes the attributes of its element.

ComposerAttachmentUploadProgressBar

PropTypeDefault
value (required)number
renderAllows you to replace the component's HTML element with a different tag, or compose it with another component.ReactElement | ((props, state) => ReactElement)

Also takes the attributes of its element.

ComposerDropOverlay, ComposerDropOverlayLabel, ComposerDropOverlayPrompt

PropTypeDefault
renderAllows you to replace the component's HTML element with a different tag, or compose it with another component.ReactElement | ((props, state) => ReactElement)

Also takes the attributes of its element.

ComposerIconButton

PropTypeDefault
loadingboolean
loadingLabelstring
focusableWhenDisabledWhether the button should be focusable when disabled.booleanfalse
nativeButtonWhether 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>`).booleantrue
renderAllows you to replace the component's HTML element with a different tag, or compose it with another component.ReactElement | ((props, state) => ReactElement)

Also takes the attributes of its element.

ComposerInput, ComposerMeta, ComposerRoot

PropTypeDefault
renderAllows you to replace the component's HTML element with a different tag, or compose it with another component.ReactElement | ((props, state) => ReactElement)

Also takes the attributes of its element.

ComposerSubmit

PropTypeDefault
loadingboolean
loadingLabelstring
size"xs" | "sm" | "md" | "lg" | "xl" | "inline" | "icon-xs" | "icon-sm" | "icon-md" | "icon-lg"
focusableWhenDisabledWhether the button should be focusable when disabled.booleanfalse
nativeButtonWhether 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>`).booleantrue
renderAllows you to replace the component's HTML element with a different tag, or compose it with another component.ReactElement | ((props, state) => ReactElement)

Also takes the attributes of its element.

ComposerSuggestionAside, ComposerSuggestionContent

PropTypeDefault
renderAllows you to replace the component's HTML element with a different tag, or compose it with another component.ReactElement | ((props, state) => ReactElement)

Also takes the attributes of its element.

ComposerSuggestionGroup

PropTypeDefault
separatedbooleanfalse
renderAllows you to replace the component's HTML element with a different tag, or compose it with another component.ReactElement | ((props, state) => ReactElement)

Also takes the attributes of its element.

ComposerSuggestionGroupLabel

PropTypeDefault
renderAllows you to replace the component's HTML element with a different tag, or compose it with another component.ReactElement | ((props, state) => ReactElement)

Also takes the attributes of its element.

ComposerSuggestionIcon

PropTypeDefault
variant"inline" | "avatar" | "framed" | "auxiliary""avatar"
renderAllows you to replace the component's HTML element with a different tag, or compose it with another component.ReactElement | ((props, state) => ReactElement)

Also takes the attributes of its element.

ComposerSuggestionList

PropTypeDefault
renderAllows you to replace the component's HTML element with a different tag, or compose it with another component.ReactElement | ((props, state) => ReactElement)

Also takes the attributes of its element.

ComposerSuggestionMeta

PropTypeDefault
variant"code" | "text""text"
renderAllows you to replace the component's HTML element with a different tag, or compose it with another component.ReactElement | ((props, state) => ReactElement)

Also takes the attributes of its element.

ComposerSuggestionOption, ComposerSuggestionTitle, ComposerToolbar, ComposerToolbarLabel

PropTypeDefault
renderAllows you to replace the component's HTML element with a different tag, or compose it with another component.ReactElement | ((props, state) => ReactElement)

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.
PropTypeDefault
children (required)ReactNode
id (required)string
label (required)string

SortableComposerAttachments, SortableComposerAttachmentsScroll

Wrap / flex-pack attachment reorder (mixed widths).
PropTypeDefault
children (required)ReactNode
items (required)readonly string[]
onReorder (required)(items: string[]) => void

Changes

  • 0.1.0 · AddedGraduated AppRail, AppShell (with sidebar collapse trigger), ChatSidebar, Composer (including sortable attachments), and Resizable* to the stable entry.