Skip to content

ChatComposer

ChatComposer

Category: Chat

const [pending, setPending] = useState(false);
<ChatComposer
isSending={pending}
beforeInput={
<IconButton aria-label="Attach">
<PaperclipIcon />
</IconButton>
}
attachments={files.map((f) => (
<AttachmentTile key={f.id} name={f.name} size={f.size} onRemove={() => remove(f.id)} />
))}
onSend={async (text) => {
setPending(true);
await send(text);
setPending(false);
}}
/>;

Install: @helix-ui/core

import { ChatComposer } from '@helix-ui/core';

status: stable · since: 0.3.0

Tags: input, message, send, ai, multiline

Anatomy

┌────────────────────────────────────────┐
│ [📎] | Type a message... [↑] │
│ (auto-resize, Enter sends) │
└────────────────────────────────────────┘

Layout

  • display — block
  • width — fill
  • height — content
  • intrinsicSize — min ~48px, grows up to maxHeight on multi-line
  • stackable — false
  • fullBleed — false

Visual

A rounded surface (radius.lg) holding a multi-line textarea, optional attachment button on the left, and a brand-colored send button on the right. Auto-grows up to a max height, then scrolls inside. Enter sends; Shift+Enter inserts a newline.

Props

NameTypeDefaultDescription
valuestring—Controlled message text.
defaultValuestring—Uncontrolled initial text.
onChange(v: string) => void—Called on every keystroke.
onSend(v: string) => void—Called when the user presses Enter or clicks send. Receives the trimmed message.
placeholderstringSend a message…Placeholder.
isDisabledbooleanfalseDisable the entire composer.
isSendingbooleanfalseWhile true, the send button is disabled (use during request).
attachmentsReactNode—Slot above the input — attachment chips, mentions.
beforeInputReactNode—Slot to the left of the input (formatting toolbar, attach button).
afterInputReactNode—Slot to the right of the input, before the send button.
sendIconReactNode—Override the default paper-plane icon.
sendLabelstringSendaria-label on the send button.
minRowsnumber1Auto-resize floor.
maxRowsnumber8Auto-resize ceiling; scrolls beyond.

Tokens used

color.bg.surface.default, color.bg.surface.muted, color.bg.action.brand.default, color.bg.action.brand.hover, color.bg.action.neutral.default, color.text.primary, color.text.on.brand, color.text.muted, color.border.default, color.border.focus, radius.lg, radius.full, space.1, space.2, space.3, font.family.sans, font.size.md

Accessibility

Keyboard

KeyAction
EnterSend the message.
Shift+EnterInsert newline.

Notes

  • The send button is disabled when the message is empty or whitespace.
  • IME composition is respected — Enter does not send while composing (Korean/Japanese/Chinese input).

Composes with

ComponentRelationNote
AttachmentTilechildRenders attached files inside the composer.
PromptSuggestionssiblingSuggestions appear above the composer.
ChatListsiblingSits below the chat list scroll area.
ChatMessagesibling
IconButtonsibling

Prompt examples

These are the AI prompt → JSX mappings used by the helix-ui prompt DSL and integrations like Cursor / Claude Code.

AI chat input

“composer that sends on Enter”

<ChatComposer onSend={handleSend} placeholder="Ask anything…" />

with attachments

“composer with paperclip”

<ChatComposer
onSend={handleSend}
beforeInput={
<IconButton aria-label="Attach" onClick={handleAttach}>
📎
</IconButton>
}
/>