import {
Channel,
ChannelHeader,
MessageComposer,
MessageList,
Thread,
WithComponents,
Window,
} from "stream-chat-react";
const CustomInput = () => <div className="custom-input-shell">...</div>;
const App = () => (
<Channel>
<Window>
<ChannelHeader />
<MessageList />
<WithComponents overrides={{ MessageComposerUI: CustomInput }}>
<MessageComposer />
</WithComponents>
</Window>
<Thread />
</Channel>
);Input UI
The Input UI component consumes MessageComposerContext and renders the visible composer. The default implementation is MessageComposerUI.
Best Practices
- Start from the exported building blocks before replacing the whole composer.
- Scope
WithComponentsaround a specific composer subtree when only one layout should differ. - Use
WithComponentsfor shared subcomponent overrides. - Keep the preview stack and textarea together so autocomplete, uploads, and recording state remain coherent.
- If you want the default slash-command UX, keep
CommandChipnext toTextareaComposerand hide attachment or extra-action controls while a command is selected. - Reuse the default
str-chat__message-composer*classes if you want to keep the built-in styling.
Basic Usage
Use WithComponents and the MessageComposerUI override key to replace the whole composer UI for a specific subtree.
UI Customization
MessageComposerUI is a good reference for a custom composer. In v14 the default structure is:
AttachmentSelector- preview stack:
- the edited-message preview (internal, not exported) or
QuotedMessagePreview - the voice-recording preview slot (
VoiceRecordingPreviewSlot) AttachmentPreviewListLinkPreviewList
- the edited-message preview (internal, not exported) or
- text and action controls:
CommandChipTextareaComposer- the send-to-channel checkbox (
SendToChannelCheckbox, internal, not exported) and the additional/extra action menus (MessageComposerActions/AdditionalMessageComposerActions)
Most of those pieces are exported building blocks. The edited-message preview and the send-to-channel checkbox are the exceptions — they are rendered internally by MessageComposerUI and are not exported from stream-chat-react, so a custom layout supplies its own controls for those. When voice recording is enabled the default UI swaps the composer for the recorder, so mirror that with recordingController and the exported AudioRecorder:
import {
AttachmentPreviewList,
AttachmentSelector,
AudioRecorder,
LinkPreviewList,
MessageComposer,
QuotedMessagePreview,
SendButton,
TextareaComposer,
WithComponents,
WithDragAndDropUpload,
useMessageComposerContext,
} from "stream-chat-react";
const CustomInput = () => {
const { handleSubmit, recordingController } = useMessageComposerContext();
return (
<WithDragAndDropUpload
className="str-chat__message-composer-container"
component="div"
>
<div className="str-chat__message-composer">
{recordingController.recordingState ? (
<AudioRecorder />
) : (
<>
<AttachmentSelector />
<div className="str-chat__message-composer-compose-area">
<div className="str-chat__message-composer-previews">
<QuotedMessagePreview />
<AttachmentPreviewList />
<LinkPreviewList />
</div>
<div className="str-chat__message-composer-controls">
<TextareaComposer />
<SendButton sendMessage={handleSubmit} />
</div>
</div>
</>
)}
</div>
</WithDragAndDropUpload>
);
};
const Composer = () => (
<WithComponents overrides={{ MessageComposerUI: CustomInput }}>
<MessageComposer />
</WithComponents>
);When a slash command is selected, the default MessageComposerUI adds str-chat__message-composer--command-active, keeps the selected command visible through CommandChip, and collapses both AttachmentSelector and AdditionalMessageComposerActions. Mirror that behavior in your own layout only if you want parity with the default composer.
For a shared override across the app, register the input UI with WithComponents:
import { Channel, MessageComposer, WithComponents } from "stream-chat-react";
const CustomInput = () => <div className="custom-input-shell">...</div>;
const App = () => (
<WithComponents overrides={{ MessageComposerUI: CustomInput }}>
<Channel>
<MessageComposer />
</Channel>
</WithComponents>
);Props
| Prop | Description | Type |
|---|---|---|
| none | The Input UI component itself does not receive required props. It should read the composer state it needs from useMessageComposerContext(), useMessageComposerController(), and the exported message-composer hooks. | - |