Skip to content
Kura UI

Composer

beta

Prompt input for chat and agents: auto-growing textarea, attachments, toolbar slot, send→stop button, paste/drop files and a length hint.

@kura-ui/react/composerCopy page for agents.mdregistry.json

Default layout

minimal

Controlled, with model picker and tools

Controlled text, simulated submitted → streaming lifecycle with stop, file limits, a character limit and tool toggles.

full

Enter to send · Shift+Enter for a new line

Submitted, streaming, over limit and disabled

state
120 character limit

New chat home

An empty-chat start: ModelPicker and VoiceDictation in the toolbar, PromptSuggestions chips that fill and focus the input.

composition

What are we working on?

Start typing, dictate, or pick a starting point.

Installation

npx shadcn@latest add https://kuro.abstergo.fyi/r/composer.json

Copies the source into components/kura/composer with its dependencies (kura-theme, attachment, icon-button, kura-merge-refs, kura-motion, tooltip, use-controllable-state, use-reduced-motion, utils). Tokens come from the kura-theme item.

When to use it

Use it for

  • The message box of a chat UI or agent console (“Ask anything…”).
  • Any prompt field that should send on Enter, accept pasted or dropped files, and stop a streaming response.
  • A prompt input that hosts a model picker, tool toggles, voice input or a slash-command menu in its toolbar.

Reach for something else when

  • A single-line search or filter field — use a plain input or command-palette.
  • Showing attachments outside an input (sent messages, file lists) — use attachment.
  • Picking a model on its own — use model-picker.
  • Voice-only input — use voice-dictation (it can also feed a composer).

Props

PropTypeDefaultDescription
valuestring—Controlled text.
defaultValuestring""Initial text when uncontrolled.
onValueChange(value: string) => void—Called on every text change.
attachmentsAttachmentItem[]—Controlled attachments ({ id, name, size?, type?, url?, status?, progress?, error?, file? }).
defaultAttachmentsAttachmentItem[]—Initial attachments when uncontrolled.
onAttachmentsChange(attachments: AttachmentItem[]) => void—Called when files are added or removed.
onFilesAdded(items: AttachmentItem[]) => void—Called with newly accepted files (paste, drop or picker) — start uploads here and update `attachments` with status/progress.
onFilesRejected(files: File[]) => void—Called with files rejected by accept, maxFileSize or maxFiles.
onSubmit(message: ComposerMessage) => void—Send handler: receives { text (trimmed), attachments }. Not called while empty, over maxLength, uploading or busy.
onStop() => void—Called by the stop button and by Escape while status is submitted or streaming.
status"idle" | "submitted" | "streaming" | "error""idle"Request lifecycle. submitted = waiting for the first token (stop button with a travelling arc), streaming = model writing (stop button).
disabledbooleanfalseDisable typing, attaching and sending (60% opacity).
maxLengthnumber—Character limit. The hint turns amber at 90% and red above; sending is blocked when over.
submitKey"enter" | "mod+enter""enter"enter = Enter sends and Shift+Enter adds a newline; mod+enter = ⌘/Ctrl+Enter sends and Enter adds a newline.
acceptstring | false""Allowed files, like <input accept> ("image/*,.pdf"). "" accepts any file; false disables attachments entirely.
maxFilesnumber—Maximum number of attachments.
maxFileSizenumber—Maximum size of one file in bytes.
clearOnSubmitbooleantrueClear text and attachments after a submit.
placeholderstring—Placeholder for the default layout (only used when Composer has no children).

All native attributes of the root element, className and ref pass through.

Anatomy

  1. ComposerRoot and drop target. data-status, data-focused, data-dragging, data-disabled; aria-busy while submitted/streaming. With no children it renders the complete default layout.data-slot="composer"
  2. ComposerAttachmentsThe attachment row (renders nothing when empty). Uses attachment's AttachmentList.data-slot="composer-attachments"
  3. ComposerInputAuto-growing <textarea>. Enter/Shift+Enter, IME-safe, paste files, Escape stops. Accepts all textarea props.data-slot="composer-input"
  4. ComposerToolbarBottom row; holds ComposerTools (left) and ComposerActions (right).data-slot="composer-toolbar"
  5. ComposerToolsLeft group: attach button, model-picker, tool toggles.data-slot="composer-tools"
  6. ComposerActionsRight group: hint, voice-dictation, submit.data-slot="composer-actions"
  7. ComposerAttachButtonPaperclip icon button that opens the file picker.data-slot="composer-attach"
  8. ComposerHintCharacter count with a mini gauge (with maxLength) or a ~token estimate. data-level = ok | near | over.data-slot="composer-hint"
  9. ComposerSubmitSend button (ink when ready, quiet when empty) that becomes a stop button while busy; data-mode = send | stop.data-slot="composer-submit"

Accessibility

  • The textarea is labelled “Message” by default (override with aria-label or aria-labelledby) and has enterkeyhint=send.
  • Over the character limit the textarea gets aria-invalid and the hint tells screen readers the limit was exceeded (announced once, not per keystroke).
  • The submit button swaps its accessible name between “Send message” and “Stop generating”; the root sets aria-busy while a response is pending.
  • The focus glow is on the whole composer (focus-within), so the textarea's own outline is replaced, never just removed.
  • Attachments are a labelled list with “Remove <file>” buttons; removing returns focus to the textarea.
  • Reduced motion: The send/stop icon swap and attachment enter/exit become plain fades (no scale or blur); the pending arc stops travelling and pulses instead.
KeysAction
EnterSend (submitKey=enter). Ignored while an IME is composing.
Shift+EnterInsert a newline
Mod+EnterSend (both submitKey modes)
EscapeStop the response while submitted/streaming
Mod+VPaste text, or attach pasted files/images

Notes for agents

  • Drive `status` from your chat hook: set "submitted" when you send, "streaming" on the first token, back to "idle" when done; pass `onStop` to abort.
  • With the Vercel AI SDK: status maps 1:1 from useChat's status ('ready' → 'idle').
  • Compose parts in this order: ComposerAttachments, ComposerInput, ComposerToolbar > (ComposerTools, ComposerActions > ComposerHint, ComposerSubmit).
  • Use `useComposer()` inside custom parts to read value, setValue, attachments, addFiles, submit, status.
  • Attachment object URLs are revoked when removed, not when sent — the sent message may still display them.
  • To insert text from voice-dictation or prompt-suggestions, control `value` and append to it.