MessageComposer

MessageComposer provides the message-composer state, controls, and default UI for composing messages. It renders MessageComposerContext for input UI components and uses MessageComposerUI by default.

Best Practices

  • Keep MessageComposer under Channel so composer state, permissions, and channel actions stay in sync.
  • Scope WithComponents around the specific MessageComposer or Thread subtree when one composer instance should differ.
  • Use WithComponents for shared composer subcomponent overrides such as AttachmentSelector, LinkPreviewList, or AudioRecorder.
  • Prefer composer middleware or overrideSubmitHandler for send-pipeline logic, not for layout customization.
  • Use messageComposer.threadId to detect thread composition instead of relying on deprecated isThreadInput.

Basic Usage

import {
  Channel,
  ChannelHeader,
  MessageComposer,
  MessageList,
  Thread,
  Window,
} from "stream-chat-react";

const App = () => (
  <Channel>
    <Window>
      <ChannelHeader />
      <MessageList />
      <MessageComposer />
    </Window>
    <Thread />
  </Channel>
);

UI Customization

MessageComposer renders the MessageComposerUI component from ComponentContext. That UI reads data from MessageComposerContext.

Replace the whole composer UI for one scoped subtree

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>
);

Provide a shared input UI with WithComponents

import {
  Channel,
  ChannelHeader,
  MessageComposer,
  MessageList,
  Thread,
  Window,
  WithComponents,
} from "stream-chat-react";

const CustomInput = () => <div className="custom-input-shell">...</div>;

const App = () => (
  <WithComponents overrides={{ MessageComposerUI: CustomInput }}>
    <Channel>
      <Window>
        <ChannelHeader />
        <MessageList />
        <MessageComposer />
      </Window>
      <Thread />
    </Channel>
  </WithComponents>
);

Props

PropDescriptionType
additionalTextareaPropsAdditional props forwarded to TextareaComposer.Omit<React.TextareaHTMLAttributes<HTMLTextAreaElement>, "defaultValue" | "style" | "disabled" | "value">
asyncMessagesMultiSendEnabledWhen enabled, recorded voice messages stay in the composer preview stack instead of being sent immediately after recording completes. Defaults to false.boolean
audioRecordingConfigCustom recording configuration for voice messages.CustomAudioRecordingConfig
audioRecordingEnabledEnables voice-message recording UI. Defaults to false.boolean
emojiSearchIndexCustom emoji search implementation used by autocomplete and emoji replacement.ComponentContextValue["emojiSearchIndex"]
focusFocuses the textarea on mount. Defaults to false.boolean
hideSendButtonHides the default send button inside the current input UI. Defaults to false.boolean
maxRowsMaximum number of rows the default TextareaComposer can grow to. Defaults to 10.number
minRowsMinimum number of rows the default TextareaComposer starts with.number
overrideSubmitHandlerOverrides the default send flow for new messages.(params: { cid: string; localMessage: LocalMessage; message: Message; sendOptions: SendMessageOptions; }) => Promise<void> | void
parentParent message used when composing a thread reply.LocalMessage
shouldSubmitOverrides the default submit shortcut. By default, Enter submits and Shift+Enter inserts a new line.(event: React.KeyboardEvent<HTMLTextAreaElement>) => boolean

Examples

Override the submit handler

import { MessageComposer, type MessageComposerProps } from "stream-chat-react";

const CustomMessageComposer = () => {
  const overrideSubmitHandler: MessageComposerProps["overrideSubmitHandler"] =
    async ({ cid, localMessage, message, sendOptions }) => {
      // custom logic here
      await sendMessageToBackend({ cid, localMessage, message, sendOptions });
    };

  return <MessageComposer overrideSubmitHandler={overrideSubmitHandler} />;
};

sendMessageToBackend in the example above is app-owned code.