Where the person talks back
The chat input shell: a context note, an attachments row, an auto-growing textarea, a leading actions slot, and a send button in the action colour. Send is the one primary CTA in the chat set, so it is the one place the action colour appears. While a response streams, send becomes stop and Enter goes inert.
The shell
The shell is the control. The textarea inside is borderless, so the border carries hover and focus for the whole surface. Enter sends, Shift and Enter breaks the line, and whitespace alone never sends. Submitting reports the value but does not clear it; the consumer owns the value and empties it after a successful send.
Send and stop
While a response streams, the send button becomes a stop button and submitting is blocked, so a person can never fire a message into a running answer. On a glowing composer the gradient ring also stays lit and keeps turning while the agent works, focus or not. Try it: send something and the demo streams for a few seconds.
Idle. Type a message and send it.
One row in a chat
A composer with nothing to hold in its footer drops the footer: inline="chat" puts a full-size send button on the text's line, with room around it. ChatWidget chooses for you. It takes this row when there is no model picker, attachments or composer actions, and the two-row composer when there is any of them. A context note is not footer content: on its own, it takes a line over the row.
AI glow
With aiGlow, focusing the composer replaces the plain selected border with AiButton's signature: the glow blooms out of the send corner and the ring fades up a beat behind it, the system's signal that a model answers here. Blur collapses it back. Off by default; the site chat turns it on. Click into the field to see it.
Context
The context prop pins a quiet, non-interactive note to the top of the shell, telling the person what the model can see. The site chat uses it to name the page you are reading. An optional contextIcon puts a Material Symbol at its left. The note is one line: a long one clips with an ellipsis, a short one carries no trailing dots.
Set contextPlacement to above and the note leaves the shell for its own bar, a small gap above it, so it reads as what the model can see rather than part of the message. The icon, the typed text and the model picker's label then start on one line.
A note that follows what the reader picked, such as rows they ticked, can offer the way back. Pass onContextClear and the chip ends in a clear button, named by contextClearLabel; here it returns the note to the page.
Attachments
Give the composer a way to hear about files and it grows an attach button, accepts files pasted into the field, and takes files dropped on it. The queue is a row of square tiles that scrolls sideways. The list stays the caller’s, the same philosophy as FileInput: the composer shows the files and reports what changed. Try it with files of your own. They are read in your browser and go nowhere.
Growth
The textarea starts at one row and grows with its content up to twelve rows, on the two-row composer and the one-row chat composer alike, then scrolls internally. maxRows sets a different cap. Add lines to feel it.
Mentions
Pass mentions, one source per trigger character, and typing @ or / at the start of a word opens a menu of that source from the line it was typed on. Arrows move, Enter or Tab writes the choice, Escape closes. The Mention page covers the tag, the menu and the sent message.
The full footer
The intended stack: a PromptSuggestions row above the composer, with attachments and a model picker in its footer. Tapping a suggestion fills the input; sending clears it.
Nothing sent yet.
The mini composer
Everything above is for a chat. The mini composer is for AI input inside other content, such as TableToolbar's AI command, and is never a chat's composer. inline sets it at exactly the control height of an Input or toolbar beside it, and size matches the table's default or compact density. It grows to three rows as a long request wraps, with send held in the bottom corner.
// From the npm package
import { Composer } from 'rift-ds/components/Composer/Composer';
// Or as source you own, via the shadcn CLI
npx shadcn@latest add https://rift-ds.com/r/composer.json