Message composer UI
The message composer UI is where a user writes, edits and sends a message. The message being written lives in a MessageComposer object from stream-chat: its text, attachments, quoted message, poll and location. The UI renders that object and calls it when the user acts. This page covers which composer the UI drives, the parts of the UI and what each part reads. The props are in the MessageComposer reference, and the object is on Message composer.

Which composer it drives
<MessageComposer /> and every part inside it work on the composer that useMessageComposerController() returns. The hook picks the first of:
- a composer supplied with
MessageComposerControllerProvider, for a composition you own, such as editing a message in a dialog; - the thread's composer,
thread.messageComposer, inside aThread; - the channel's composer,
channel.messageComposer.
Editing uses the same composer. When the user picks "Edit" on a message, the action saves what the composer holds and loads the message into it. The composer is then in edit mode: editedMessage is set in its state, the edited-message preview shows, and sending saves the edit. Cancelling the edit puts back what the user was writing before.
When MessageComposer unmounts, it saves a draft of the thread's or channel's composer, if drafts are turned on, and clears it. A supplied composer is saved but never cleared; it belongs to whoever supplied it. Drafts covers drafts.
Previews
Above the input, the composer shows what the message will carry. Each preview renders only when the composer has something for it:
| Preview | Shows | Override key |
|---|---|---|
| Edited message | The message being edited, with ✕ to cancel. Takes the quoted message's place while editing. | EditedMessagePreview |
| Quoted message | The message being replied to, with ✕ to remove it | QuotedMessagePreview |
| Voice recording | A recording waiting to be sent | VoiceRecordingPreviewSlot |
| Attachments | Files, images and videos, with their upload progress | AttachmentPreviewList |
| Link previews | A card for each link in the text, while it loads and once loaded | LinkPreviewList |
| Location | A location to share. It can't be removed while editing. | None |
The input
TextareaComposer is the text input. It reads and writes textComposer: the text, the cursor position, the mentioned users and the active command.
Typing a trigger opens the suggestion list, SuggestionList: @ suggests people and groups to mention, / suggests commands, and : suggests emoji once you add the emoji middleware from stream-chat-react/emojis to the text composer. Each item is a SuggestionListItem. The suggestions come from the text composer's middleware; Suggestions explains them.
Picking a command, such as /giphy, shows it as a chip before the input, CommandChip. The chip's ✕ clears the command.
Actions
The attachment selector, AttachmentSelector, sits before the input. It opens a menu to upload a file, create a poll, share a location or pick a command. The menu is an action set, explained in Actions.
After the input come two areas. The first, AdditionalMessageComposerActions, shows the emoji picker when you register one as the EmojiPicker override, except during a cooldown. The second shows one control, picked in this order:
- The cooldown timer,
CooldownTimer, while slow mode stops the user from sending. It shows the seconds left. See Slow mode. - The record button,
StartRecordingAudioButton, when the composer is empty, nothing is being edited, no command is active, voice recording is turned on with<MessageComposer audioRecordingEnabled />, and the browser can record. While the user records,AudioRecorderreplaces the whole composer. See Voice recordings and audio playback. - The stop button,
StopAIGenerationButton, while an AI is thinking or generating a reply in the channel. It callsChannel.stopAIResponse(). RegisterStopAIGenerationButton: nullto turn it off. See SDK integration. - The send button,
SendButton, otherwise. It is disabled until the composer has something to send, and shows a checkmark while editing or with a command.
In threads
In a thread, the composer shows a checkbox, SendToChannelCheckbox, that also posts the reply to the channel. It reads "Also send in channel", or "Also send as a direct message" in a channel with two members. It toggles showReplyInChannel in the composer's state, and is hidden while editing.
Drag and drop
Files dropped on the composer are uploaded into it. The composer UI wraps itself in WithDragAndDropUpload, which shows "Drag your files here" while files are dragged over it, and refuses the files that attachments.acceptedFiles in the composer configuration doesn't accept.
To accept drops on a larger area, such as the whole channel, wrap that area in WithDragAndDropUpload. Files dropped anywhere in it go to the composers inside:
import {
Channel,
ChannelHeader,
MessageComposer,
MessageList,
WithDragAndDropUpload,
} from "stream-chat-react";
export const ChannelWithDropZone = () => (
<Channel channel={channel}>
<WithDragAndDropUpload className="channel-drop-zone" component="section">
<ChannelHeader />
<MessageList />
<MessageComposer />
</WithDragAndDropUpload>
</Channel>
);The composer's own drop zone turns off during a cooldown and when the user can't upload files. The props are in the WithDragAndDropUpload reference.
What each part reads
Each part subscribes to the store of the composer piece it shows, and calls that piece when the user acts:
| Composer piece | Used by |
|---|---|
state |
The quoted and edited message previews, the send button, the also-send-to-channel checkbox |
textComposer |
The input, the suggestion list, the command chip |
attachmentManager |
The attachment and voice recording previews, drag and drop, the attachment selector |
linkPreviewsManager |
The link previews |
pollComposer |
The poll creation dialog |
locationComposer |
The location preview and the share location dialog |
customDataManager |
No built-in part. It holds custom fields that your own parts add to the message. |
A part of your own reads the same way: get the composer with useMessageComposerController() and subscribe to a store with useStateStore. This one counts the characters typed, beside the emoji picker:
import type { TextComposerState } from "stream-chat";
import {
AdditionalMessageComposerActions,
MessageComposer,
useMessageComposerController,
useStateStore,
WithComponents,
} from "stream-chat-react";
const textSelector = ({ text }: TextComposerState) => ({ text });
const CharacterCount = () => {
const { textComposer } = useMessageComposerController();
const { text } = useStateStore(textComposer.state, textSelector);
return <span className="character-count">{text.length}</span>;
};
// Renders the default part too, so the emoji picker stays.
const ActionsWithCount = () => (
<>
<CharacterCount />
<AdditionalMessageComposerActions />
</>
);
const overrides = { AdditionalMessageComposerActions: ActionsWithCount };
export const ComposerWithCount = () => (
<WithComponents overrides={overrides}>
<MessageComposer />
</WithComponents>
);The same part works in a channel, in a thread and while editing, because the hook returns the composer of the place it is rendered in.
Replacing parts
Register replacements with WithComponents. Overriding components explains how overrides apply.
| Override key | Replaces |
|---|---|
MessageComposerUI |
The whole composer UI. MessageComposer still provides the composer. |
EditedMessagePreview, QuotedMessagePreview |
The edited and quoted message previews |
AttachmentPreviewList, VoiceRecordingPreviewSlot |
The attachment and voice recording previews |
LinkPreviewList |
The link previews |
TextareaComposer |
The input |
AutocompleteSuggestionList, AutocompleteSuggestionItem |
The suggestion list and each suggestion |
CommandChip |
The command chip |
AttachmentSelector |
The attachment selector |
AttachmentSelectorInitiationButtonContents |
The contents of the button that opens the selector |
PollCreationDialog, ShareLocationDialog |
The dialogs the selector opens |
AdditionalMessageComposerActions, EmojiPicker |
The area after the input, and the emoji picker in it |
SendButton |
The send button. Gets sendMessage. |
CooldownTimer |
The cooldown timer |
StartRecordingAudioButton, AudioRecorder |
The record button and the recorder |
StopAIGenerationButton |
The stop button. null turns it off. |
SendToChannelCheckbox |
The also-send-to-channel checkbox |
The defaults of EditedMessagePreview, SendToChannelCheckbox and StopAIGenerationButton aren't exported, so a replacement for one of them can't render the default inside it.
The Custom composer recipe builds a composer UI of its own.