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 WithComponents around a specific composer subtree when only one layout should differ.
  • Use WithComponents for 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 CommandChip next to TextareaComposer and 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.

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

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)
    • AttachmentPreviewList
    • LinkPreviewList
  • text and action controls:
    • CommandChip
    • TextareaComposer
    • 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

PropDescriptionType
noneThe 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.-