Composer

Multi-line input + submission shell for chat. Submits on Enter (Shift+Enter inserts newline). Trailing slot for attachment + send buttons.

Basic — round-trip with local message state

assistant

Ask anything — I'll echo your input back.

With attachment slot — chips render in the trailing children region

  • assistant

    Attach files, then send — your message + chips render above.

Installation

pnpm
pnpm dlx @hex-core/cli add composer

With attachment slot

Attach button on the leading edge of the trailing slot.

tsx
<Composer value={v} onValueChange={setV} onSubmit={send}>
  <Button variant="ghost" size="icon" onClick={pickFile}><Paperclip /></Button>
  <Button type="submit">Send</Button>
</Composer>

API Reference

PropTypeDefaultDescription
valuerequired
stringControlled textarea value.
onValueChangerequired
functionCalled with the new value on each keystroke.
onSubmitrequired
functionCalled with the trimmed value on Enter or form submit.
disabled
booleanfalseLock the input and suppress submission (e.g. during streaming).
placeholder
stringTextarea placeholder copy.
submitOnEnter
booleantrueSubmit when Enter is pressed without Shift. Disable to require button click.
children
ReactNodeTrailing slot — attachment buttons, voice toggle, send button, etc.
className
stringAdditional CSS classes on the form wrapper.

AI Guidance

When to use

Wrap any user-input surface in an AI app — chatbots, AI editors, agent prompts. Pair with `useChat` from @ai-sdk/react or any equivalent state hook.

When not to use

Don't use for non-chat forms (use Form + Textarea). Don't bake fetch/streaming logic into onSubmit — keep the network call in the consumer.

Common mistakes

  • Calling onSubmit with the raw event instead of the value — onSubmit receives the trimmed string already
  • Forgetting to clear `value` after submit — Composer is fully controlled
  • Wrapping the component in <form> — Composer renders its own form element

Accessibility

Renders a real <form> + <textarea>, so Enter submission and screen-reader announcements work without extra ARIA. Pass `aria-label` on the wrapper if there's no visible label.

Token budget: 280

Verified against @hex-core/components@1.12.0