Usage
PromptInput in ui-ai-elements in behavior, API and composition, and takes its surface and radii from the Figma frame: a 12px surface with a default edge, an add control, the placeholder, and a Send control on a 6px radius. Enter sends and Shift+Enter inserts a newline. The typed area grows with its text up to six lines and then scrolls, and focus stays in it after a send. It renders no label; the screen that places it names it.
| Key | Result |
|---|---|
| Enter (any modifier but Shift) | Calls onSend, then clears the composer. Never inserts a newline |
| Shift+Enter | Inserts a newline |
| Enter while an input method is composing | Confirms the candidate. Nothing is sent |
| Backspace after an inline chip | Removes the whole chip |
| Delete before an inline chip | Removes the whole chip |
| Arrow keys beside an inline chip | Step over the whole chip; Shift extends the selection over it |
onSend(message, segments, details) receives the trimmed text and its content in order. An inline chip is @label in message and a { type: "chip", id, label } segment, so its id survives. Context chips are never part of either. The composer clears after onSend returns; call details.cancel() to keep the text for a send that can fail, then clear it yourself when the message is accepted.
value and onValueChange, or kept by the composer with defaultValue. The value is the text with each inline chip written as @label. A controlled parent stores every value it is given, including the empty one:
message you get back from a send. A value written while the typed area has focus puts the caret at its end. hasSendableText is the rule Enter uses.
handleRef.current.insertChip(chip) or from a chosen suggestion. Each is removable.chips prop: a named row above the typed area, the pinned context the message applies to. They survive a send. Give onChipDismiss to make them removable.removeLabel. A press, or Enter, Space, Backspace or Delete on the focused control, removes the chip, announces removedAnnouncement, and puts focus and the caret back in the typed area where the chip was, so typing carries on. onChipDismiss(id, details) runs first with the reason ItemPress; details.cancel() keeps the chip and leaves focus on the control.onChipDismiss runs with the reason InputChange and cannot cancel. Where the engine keeps the deletion in its undo history (Chrome and Safari), undo restores the chip with its control.@label in the text, in onValueChange and in onSend's message; segments keeps its id. Copy and cut put the selection on the clipboard the same way.status of a chat hook straight through. While it is "submitted" or "streaming", a send is blocked and the trailing control is Stop when you give onStop and stopLabel, or Send in its pending state when you do not. A disabled composer disables Stop and shows Send disabled; a read-only one keeps Stop usable.
Reflow keeps the controls on one row until the text would wrap, then drops them under it. Stacked is flush for compact surfaces: two rows from the start, a rule between them, no border, radius or shadow.
The add control rests on the translucent --color-fill-default wash rather than Neutral Soft's opaque fill, so it composes over the surface. Its hover and pressed washes are unchanged, and a disabled composer keeps Button's disabled fill.
ChatInput renders no label. Give aria-label or aria-labelledby. placeholder is copy and is not a name. Every icon-only control is named by a prop: sendLabel, addLabel, stopLabel.
onSubmit is onSend, the send, stop and add controls take required names, an attach row carries its own label and icon, and a chip carries the copy of its remove control. The full mapping, with the layout that fits each ChatPanel variant, is in Migrating from ui-ai-elements. Dictation is not carried; see the packages/ui/src/components/chat-input/CONTRACT.md.