Prompt input
Takes the next message for an agent.
Layout
stackeddefault
inline
States
empty
typing
focused
shell
working
2 queued, sent in order when the agent finishes
- QueuedThen draft the reply to Maya
- QueuedAnd tag the ticket as refunded
queued
uploading
blocked
- Out of credits for this workspace
disabled
Usage
When to use it
Rules
8 rules
- Must
- Send value.serialized to the model and keep value.mentions; never rebuild the prompt from the editor DOM. Because serialized writes each chip as [label](type:id) and each collapsed paste in full; the DOM holds chip labels and a "Pasted 12 lines" stub.
- Must
- Drive status from the run: submitted the moment it sends, streaming on the first byte, ready when it ends, and pass generation. Because Send turns into Stop from submitted, Escape stops only while working, and generation keeps a late stop from cancelling the next run.
- Never
- Put a primary Button in tools or meta; the model picker is PromptInputModel and every other tool variant="ghost". Because Send is the composer's one glass key, and a second one on the same row reads as two next steps.
- Never
- Control the text with value and setState; read it through onValueChange and drive it through the ref. Because chips are atomic nodes in a contenteditable, and re-rendering its text from state moves the caret and breaks every chip.
- Must
- Pass every file as an attachment with its status and progress, and onRetryAttachment for a failed one. Because Send waits for uploads and blocks on a failed file with its name in the status line, so nothing a person attached is dropped without a word.
- Should
- Pass messages written while the agent works as queue={<PromptQueue items={queued}/>}, with onSendQueuedNow and onEditQueued. Because the queue then sits in the composer's shell as a second panel, Enter on an empty prompt sends its head and ArrowUp edits its tail.
- Must
- Give disabled a disabledReason, and disabledAction when there is a way out. Because a composer that silently refuses input reads as broken; the reason sits in the panel and describes the editor.
- Should
- Set draftKey per thread, so a reload keeps what the person was writing. Because the draft saves 200ms after the last edit and clears on send; one key across threads would carry one thread's draft into another.
State order
7 states, highest first
- 1
- disabled
- 2
- shell
- 3
- working
- 4
- uploading
- 5
- blocked
- 6
- typing
- 7
- empty
Accessibility
5 checks
- The editor is a multi-line textbox named by label, with aria-controls and aria-activedescendant on the @ and / list.
- The status line is a status region and describes the editor; uploads, a failed file and an undo are said there.
- A chip is one node: Backspace removes it whole and Mod+Z or the Undo link puts it back.
- Focus darkens the panel's edge to the focus line; the editor itself draws no ring.
- Escape is consumed only when it closes the list, leaves shell mode, cancels a held send or stops the run.
Keyboard
6 keys
- Enter
- Sends; while working with an empty prompt, sends the queue's head.
- Shift+Enter
- Breaks the line.
- @ and /
- Open the mention list, or the command list.
- Up, Down, Enter, Tab
- Move in the open list and pick.
- Escape
- Closes the list, leaves shell mode, cancels a held send, stops the run, then blurs.
- ArrowUp on empty
- Edits the last queued message.
Edge cases
6 edge cases
- IME composition
- Nothing fires while composing, including Enter.
- A paste of 3 lines or 150 characters
- Collapses into a "Pasted 12 lines" chip, sent in full; a click expands it.
- A paste past maxLength
- Refused, with how much fits in the status line.
- A file type accept refuses
- The drop overlay turns to the failure tint and says what fits before the drop.
- Send while uploading
- Held: the status line says it sends when they finish, with Cancel.
- Phone width
- The tools take their own row under 320px; Send stays on the bottom line.
API
14 props
onSubmit(value: PromptValue, attachments) => voidonValueChange(value: PromptValue) => voidstatus, generation, onStopSendStatus, string | number, (generation) => voiddefault'ready'layout'stacked' | 'inline'default'stacked'mentions, commandsMentionItem[], CommandItem[]default[]shell, mode, onModeChangeboolean, 'normal' | 'shell'defaultfalseattachments, onAttach, onRemoveAttachment, onRetryAttachment, acceptAttachment[], handlers, stringtools, meta, voiceReactNode, ReactNode, PromptInputMicPropsqueue, onSendQueuedNow, onEditQueuedReactNode, () => void, () => booleandisabled, disabledReason, disabledActionboolean, ReactNode, ReactNodedraftKey, defaultValue, maxLengthstring, string, numberdefaultmaxLength 100,000waitingCount, onReviewWaitingnumber, () => voiddefault0refPromptInputHandlelabelsPromptInputLabelsdefaultEnglish
Tokens
19 tokens
--shell- #f1f1f0
--paper- #ffffff
--field- #efefef
--line-faint- rgb(5 5 5 / 0.06)
--line-focus- rgb(5 5 5 / 0.32)
--radius-shell- 14px
--radius-panel- 11px
--shell-inset- 3px
--control-sm- 26px
--type-body- 400 14px/1.5 var(--font-sans)
--type-code- 400 calc(14px * var(--mono-scale))/1.5 var(--font-mono)
--type-caption- 400 12px/1.35 var(--font-sans)
--text-1- rgb(5 5 5 / 0.80)
--text-2- rgb(5 5 5 / 0.60)
--text-3- rgb(5 5 5 / 0.45)
--fail- #e35e49
--fail-tint- #fbe9e5
--fail-text- #b23c29
--dur-state- 160ms