Sender

The input at the bottom of a chat, with a send button that turns into a stop button while a reply streams.

Usage

When to reach for Sender, how sending and loading work, and the parts that need your state.

When to use

Sender is the message box of a chat: a growing text input with a send button, a stop button while a reply streams, and room around it for attachments and tools.
  • The reader writes a message to an assistant or a person, and sends it with Enter.
  • The input needs chat behavior: Enter to send, a stop button while the reply is on its way, files pasted in.
Use something else when
  • The text is a form field, like a comment on a ticket submitted with the rest of a form. That is Input.TextArea, where Enter adds a line and a form button submits.
  • The input is a search box. Input.Search or AutoComplete fit, and neither has a stop state.

Sending and clearing

Handle the message in onSubmit. Enter and the send button both call it, with the text as the first argument.
  • Clear the input yourself. onSubmit leaves the text in place. Set your value to an empty string in it, or call ref.current.clear() when you do not control the value.
  • The send button is disabled while the input is empty, and Enter does nothing then. Text made of spaces still sends, so trim the message in onSubmit and ignore it when nothing is left.
  • onChange gets the text, not an event. Pass your setter straight in: onChange={setValue}.
  • Pick the send key with submitType. The default sends on Enter and breaks the line on Shift+Enter. Use shiftEnter where readers write long, multi-line messages.

While a reply streams

Set loading from the moment you send until the reply has finished. The send button becomes a stop button and Enter stops sending.
  • Pass onCancel with loading. Without it the stop button does nothing. In it, abort the request and set loading back to false.
  • The reader can keep typing while loading. Their next message waits in the input until the reply is done.
  • Use disabled for a Sender that cannot send at all, like when the reader is out of messages, and say why in the placeholder.

Prefix, suffix, header and footer

Four props place content around the input. Keep each one to what the reader needs while writing.
  • prefix holds one or two buttons, like attach or a template picker.
  • Keep a send button when you set suffix. Enter only sends while a send button is on screen to say the input has text. With suffix={false}, or a suffix with no SendButton, a Sender that mounts empty never sends on Enter. Use the function form and render the SendButton it gives you.
  • header takes a Sender.Header, a panel that opens above the input for attachments or a quoted message.
  • footer takes a row of Sender.Switch toggles, like “Deep search”, or a short hint.
Always pass open and onOpenChange. The header has no state of its own. Without open it stays closed, and its close button only calls onOpenChange with the next state.
  • Open it from a button in prefix, like a paperclip, and open it as well when a file is pasted or dropped, so the reader sees what was attached.
  • Set closable={false} for a header the reader should not dismiss, like a reply-to quote you remove yourself.
  • The content is not mounted while the header is closed. Set forceRender when you need a ref inside it, like the Attachments ref for pasted files.

Slot mode

slotConfig and skill turn the input into a template with inline fields. They suit prompts with a fixed shape, like “Translate [text] into [language]”.
  • Slot mode is uncontrolled. value and defaultValue are ignored, and slotConfig is read once. To load a new template, change the Sender's key. Do not store the slotConfig from onChange and pass it back.
  • Read what the reader filled in from onSubmit's second argument, or from ref.current.getValue().
  • Give every slot except text a unique key, and a formatResult when its value should read differently in the sent message.

Speech and pasted files

  • allowSpeech uses the browser's speech recognition, which some browsers do not have. There the microphone shows disabled, so do not make it the only way to write.
  • The first click asks for microphone permission. A reader who refuses gets a disabled button with no message, so explain it near the input if speech matters to your product.
  • onPasteFile fires only when the clipboard holds files and no text. Pass the files to an Attachments in the header and open the header.

Restyling it

Reach for the semantic keys before a descendant selector: root, content, prefix, input, suffix, footer and switch. A part with a key keeps its name across versions, and a part reached by class name may not.
  • Sender takes the full width of its container. Set a width on it with className, like w-full max-w-[32rem].
  • The border, the slot colors and the disabled send button come from Sender's own tokens, listed on the Tokens tab.