This is beta documentation for Stream Chat React SDK v14. For the latest stable version, see the latest version (v13) .

Upgrade to v14

stream-chat-react@14 keeps the core chat concepts from v13, but it significantly redesigns component structure, UI override points, and several low-level customization APIs.

If your app uses the default SDK UI with minimal customization, the upgrade is usually straightforward. If you override components, rely on context helper HOCs, customize message input behavior, or theme internal DOM selectors, plan for a focused migration pass.

Quick Migration Checklist

  1. Upgrade package versions and align stream-chat.
  2. Replace removed imports and stop importing MessageActions or Search from stream-chat-react/experimental.
  3. Move UI overrides off Channel and into WithComponents / ComponentContext.
  4. Migrate message actions, edit flows, and custom message UIs to the v14 MessageActions and MessageComposer model.
  5. Update custom message input code to use the slimmer MessageInputContext plus dedicated hooks such as useCooldownRemaining().
  6. Review attachment, gallery, reaction, poll, thread, and modal customizations for renamed props and components.
  7. Audit CSS selectors, snapshots, and DOM queries against the new markup.

Import And Entrypoint Changes

The most common direct renames are:

v13v14
MessageDeletedMessageDeletedBubble
MessageOptionsMessageActions
ChannelSearchSearch
MessageNotificationNewMessageNotification
ScrollToBottomButtonScrollToLatestMessageButton
ReactionsListModalMessageReactionsDetail
MessageIsThreadReplyInChannelButtonIndicatorMessageAlsoSentInChannelIndicator
ModalGlobalModal
AddCommentFormAddCommentPrompt
EndPollDialogEndPollAlert
SuggestPollOptionFormSuggestPollOptionPrompt

The following APIs were removed and require a redesign rather than a rename:

  • FixedHeightMessage
  • EditMessageForm
  • EditMessageModal
  • MessageActionsBox
  • MessageActionsWrapper
  • CustomMessageActionsList
  • QuotedPoll
  • QuotedMessagePreviewHeader
  • ButtonWithSubmenu

Suggested replacements for the most common removals:

Removed APIAlternative approach in v14
FixedHeightMessageprovide a custom VirtualMessage built against the current MessageUIComponentProps contract
EditMessageForm, EditMessageModalstart editing through messageComposer.initState({ composition: message }), clear with messageComposer.clear(), and customize EditedMessagePreview
MessageActionsBox, MessageActionsWrapper, CustomMessageActionsListrebuild custom actions around MessageActions, defaultMessageActionSet, messageActionSet, and ContextMenu
QuotedPoll, Poll.isQuotedmove quoted-poll rendering to the quoted-message layer with QuotedMessage, QuotedMessagePreview, or QuotedMessagePreviewUI
QuotedMessagePreviewHeadercustomize the full quoted preview through QuotedMessagePreview / QuotedMessagePreviewUI instead of composing around a separate header
ButtonWithSubmenurebuild submenu flows with ContextMenu, ContextMenuButton, ContextMenuHeader, and ContextMenuBackButton

MessageActions and Search are no longer exported from stream-chat-react/experimental. Import them from the main package:

import { MessageActions, Search } from "stream-chat-react";

The with*Context HOC wrappers were removed. Replace them with hooks inside function components:

import { useMessageContext } from "stream-chat-react";

const CustomMessage = () => {
  const { message } = useMessageContext();
  return <div>{message.text}</div>;
};

Some low-level helper and icon exports were also removed rather than renamed:

  • move emoji-only checks from isOnlyEmojis to countEmojis() or messageTextHasEmojisOnly()
  • replace the removed useAudioController() hook with useAudioPlayer()
  • replace showMessageActionsBox() / shouldRenderMessageActions() with the v14 MessageActions and messageActionSet flow
  • replace removed standalone icons such as CloseIcon, SendIcon, MicIcon, MessageSentIcon, and MessageDeliveredIcon with the public Icons set or higher-level components such as SendButton and MessageStatus
  • if you relied on attachmentTypeIconMap, inline your own map or migrate to the newer thread preview components

Examples:

Emoji-only checks:

import { countEmojis, messageTextHasEmojisOnly } from "stream-chat-react";

const emojiOnly = messageTextHasEmojisOnly(message);
const emojiCount = countEmojis(message.text);

Message action visibility:

import { MessageActions, defaultMessageActionSet } from "stream-chat-react";

const messageActionSet = defaultMessageActionSet.filter(
  ({ type }) => type !== "delete",
);

<MessageActions messageActionSet={messageActionSet} />;

Removed standalone icons:

import {
  IconCrossMedium,
  MessageStatus,
  SendButton,
  useMessageInputContext,
} from "stream-chat-react";

const CustomComposerActions = () => {
  const { handleSubmit } = useMessageInputContext();

  return (
    <>
      <button>
        <IconCrossMedium />
      </button>
      <SendButton sendMessage={handleSubmit} />
    </>
  );
};

const CustomMessageFooter = () => <MessageStatus />;

Custom attachment icon maps:

import { IconFileBend, IconImages1Alt, IconVideo } from "stream-chat-react";

const attachmentTypeIconMap = {
  file: IconFileBend,
  image: IconImages1Alt,
  video: IconVideo,
};

const AttachmentTypeIcon =
  attachmentTypeIconMap[attachment.type] ?? IconFileBend;

<AttachmentTypeIcon />;

Search Changes

ChannelSearch was removed. Search now lives in the main package entrypoint, and ChannelList renders it directly when showChannelSearch is enabled.

Before:

import { ChannelList, ChannelSearch } from "stream-chat-react";
import { Search } from "stream-chat-react/experimental";

<ChannelList
  additionalChannelSearchProps={{
    placeholder: "Search channels and users...",
    searchForChannels: true,
  }}
  ChannelSearch={ChannelSearch}
  showChannelSearch
/>;

<Search />;

After:

import { ChannelList, Search, WithComponents } from "stream-chat-react";

const CustomSearch = () => (
  <Search
    exitSearchOnInputBlur
    placeholder="Search channels, messages, and users..."
  />
);

<WithComponents overrides={{ Search: CustomSearch }}>
  <ChannelList showChannelSearch />
</WithComponents>;

Key changes to account for:

  • remove direct ChannelSearch imports
  • stop using ChannelList.additionalChannelSearchProps
  • stop using the removed ChannelList.ChannelSearch override prop
  • if you customize search inside ChannelList, move that work to WithComponents / ComponentContext with Search, SearchBar, SearchResults, and related search subcomponents
  • if you rendered the experimental Search directly, keep rendering Search, but import it from stream-chat-react

Move UI Overrides From Channel To WithComponents

In v13, many UI overrides could be passed directly to Channel. In v14, that forwarded override surface was removed from ChannelProps. Use WithComponents instead.

Before:

import {
  Channel,
  ChannelHeader,
  MessageInput,
  MessageList,
  Thread,
  Window,
} from "stream-chat-react";
// Import your custom override components from your codebase.

<Channel
  Input={CustomMessageInput}
  Message={CustomMessage}
  MessageActions={CustomMessageActions}
  Modal={CustomModal}
>
  <Window>
    <ChannelHeader />
    <MessageList />
    <MessageInput />
  </Window>
  <Thread />
</Channel>;

After:

import {
  Channel,
  ChannelHeader,
  MessageInput,
  MessageList,
  Thread,
  Window,
  WithComponents,
} from "stream-chat-react";
// Import your custom override components from your codebase.

<WithComponents
  overrides={{
    Input: CustomMessageInput,
    Message: CustomMessage,
    MessageActions: CustomMessageActions,
    Modal: CustomModal,
  }}
>
  <Channel>
    <Window>
      <ChannelHeader />
      <MessageList />
      <MessageInput />
    </Window>
    <Thread />
  </Channel>
</WithComponents>;

Important override-key changes:

v13 override keyv14 migration
MessageNotificationNewMessageNotification
ReactionsListModalMessageReactionsDetail
MessageOptionsMessageActions
MessageIsThreadReplyInChannelButtonIndicatorMessageAlsoSentInChannelIndicator
FileUploadIconAttachmentSelectorInitiationButtonContents
EditMessageInputremove old edit-flow override and customize MessageInput / EditedMessagePreview instead
EditMessageModalremoved
QuotedPollremoved

Channel also changed its empty-state behavior. If there is no active channel, v14 renders EmptyStateIndicator by default. To preserve the old blank behavior, pass:

import { Channel } from "stream-chat-react";

<Channel EmptyPlaceholder={null}>...</Channel>;

Message, Actions, And Edit Flow Changes

MessageOptions has been removed. The supported customization path is the v14 MessageActions component and its action-set model.

Before:

import { MessageOptions } from "stream-chat-react";

const CustomMessageActions = () => <MessageOptions />;

After:

import {
  MessageActions,
  defaultMessageActionSet,
  useMessageContext,
  useMessageComposer,
} from "stream-chat-react";

const CustomMessageActions = () => {
  const actions = defaultMessageActionSet.filter(
    ({ type }) => type !== "delete",
  );
  return <MessageActions messageActionSet={actions} />;
};

const CustomMessage = () => {
  const { groupedByUser, handleDelete, message } = useMessageContext();
  const messageComposer = useMessageComposer();

  return (
    <div data-grouped-by-user={groupedByUser}>
      <button
        onClick={() => messageComposer.initState({ composition: message })}
      >
        Edit
      </button>
      <button onClick={() => handleDelete({ hardDelete: true })}>Delete</button>
    </div>
  );
};

Key message-level changes:

  • customMessageActions was removed from MessageContext, Message, MessageList, and VirtualizedMessageList.
  • handleDelete changed from (event, options?) to (options?).
  • Custom Message overrides should read values such as groupedByUser, firstOfGroup, and endOfGroup from useMessageContext() instead of expecting them as injected component props.
  • MessageDeleted was replaced by MessageDeletedBubble.
  • MessageEditedTimestamp was removed. Use MessageEditedIndicator for the default edited label + tooltip, or render your own Timestamp against message.message_text_updated_at if you need a fully custom edited-time UI.
  • MessageStatusProps no longer include Avatar, and the default status UI no longer renders the old standalone status icons or reader avatar.
  • MessageTextProps.theme was removed, and MessageText no longer owns quoted-message rendering.
  • MessageTimestamp now defaults to time-only formatting (HH:mm). If you want the old calendar-style default back, override timestamp/MessageTimestamp in Streami18n or provide a custom MessageTimestamp.

Edit-message flows now run through MessageComposer:

  • remove EditMessageForm, EditMessageModal, useEditHandler, and clearEditingState
  • start editing with messageComposer.initState({ composition: message })
  • cancel or clear the edit state with messageComposer.clear()
  • customize the edit preview through EditedMessagePreview

Message-list floating indicators and unread UI also changed in v14:

  • MessageNotification became NewMessageNotification
  • ScrollToBottomButton became ScrollToLatestMessageButton
  • MessageListNotifications is now notifications-only; it no longer receives the old unread-count and scroll-control contract
  • client-side notifications now render through NotificationList, while MessageListNotifications is the narrower channel-notification plus connection-status container
  • MessageList and VirtualizedMessageList now render NewMessageNotification and ScrollToLatestMessageButton directly instead of routing them through MessageListNotifications
  • UnreadMessagesSeparator now shows the unread count by default and includes a mark-read button; pass showCount={false} or provide a custom separator if you want the old simpler behavior
  • the default message action order and action set also changed in v14, so define your own messageActionSet explicitly if your UX depends on a stable action menu

Message Input And Composer Changes

v14 keeps MessageInputContext, but it is much smaller. Most composition state now lives in MessageComposer and dedicated hooks.

Before:

import { useMessageInputContext } from "stream-chat-react";

const { cooldownRemaining, handleSubmit, setCooldownRemaining } =
  useMessageInputContext();

After:

import {
  useCooldownRemaining,
  useIsCooldownActive,
  useMessageInputContext,
} from "stream-chat-react";

const CustomComposerFooter = () => {
  const { handleSubmit } = useMessageInputContext();
  const cooldownRemaining = useCooldownRemaining();
  const isCooldownActive = useIsCooldownActive();

  return (
    <button disabled={isCooldownActive} onClick={() => void handleSubmit()}>
      Send ({cooldownRemaining})
    </button>
  );
};

Key composer changes:

  • handleSubmit now accepts only an optional event. It no longer accepts customMessageData or SendMessageOptions.
  • The default CooldownTimer is now a zero-prop component:
import { CooldownTimer } from "stream-chat-react";

<CooldownTimer />;
  • Custom message data should be added through composer middleware or messageComposer.customDataManager.
  • LinkPreviewList now shows one preview by default and no longer suppresses previews while quoting a message.
  • The default textarea now grows up to 10 rows instead of 1. Pass maxRows={1} if you need the old behavior.
  • AttachmentSelector was redesigned. Custom attachmentSelectorActionSet logic should be reviewed for the new action model, command submenu support, and cooldown-disabled trigger behavior.
  • the default attachment selector can now add a selectCommand action, and runtime-disabled upload actions are filtered out instead of being shown disabled
  • the default composer now hides the attachment selector and additional composer actions while a slash command is selected; custom input UIs should decide whether to mirror that command-active layout
  • Voice recordings moved out of AttachmentPreviewList into a dedicated VoiceRecordingPreviewSlot.
  • QuotedMessagePreviewHeader was removed. If you customized quoted previews, override QuotedMessagePreview or QuotedMessagePreviewUI instead of trying to swap only the old header fragment.
  • AutocompleteSuggestionItem now follows the redesigned suggestion-list item contract and should not assume the old internal list-item wrapper.

Attachment customization changed in a few different layers.

At the high level:

  • AttachmentProps.Gallery became AttachmentProps.ModalGallery
  • AttachmentProps.Media now uses VideoPlayerProps
  • image and video grouping now flows through the newer media path instead of the old gallery / image split

If you render ModalGallery directly, migrate from the old images / index API to the new items-based API:

import { ModalGallery } from "stream-chat-react";

<ModalGallery items={galleryItems} />;

Gallery itself is no longer the old thumbnail-grid-plus-lightbox component. In v14 it is the provider-style carousel layer, and it only renders visible UI when a GalleryUI is supplied:

import { Gallery, GalleryUI } from "stream-chat-react";

<Gallery GalleryUI={GalleryUI} items={galleryItems} />;

If your old customization wrapped Gallery directly, decide whether you actually want:

  • ModalGallery for the old “thumbnail grid that opens a viewer” behavior
  • Gallery plus a custom GalleryUI for a custom carousel/viewer implementation

If you use lower-level attachment primitives, review these changes too:

  • MediaContainer now works with attachments instead of a single attachment
  • gallery payloads changed from images to items
  • audio attachment props changed from og to attachment
  • CardAudio is no longer re-exported from the package root
  • native giphy attachments now stay inline through Giphy; they do not expand through ModalGallery

FileIcon was simplified:

v13v14
filenamefileName
big, size, sizeSmall, typeremoved
mimeTypeToIcon(type, mimeType)mimeTypeToIcon(mimeType?)

Reactions, Polls, And Dialogs

The reactions and polls surfaces changed enough that custom wrappers usually need code changes.

Reactions:

  • rename ReactionsListModal to MessageReactionsDetail
  • treat MessageReactionsDetail as dialog content, not as the old standalone modal component
  • custom ReactionSelector components can no longer rely on the old prop surface such as reactionOptions, latest_reactions, reaction_counts, or Avatar
  • if you customize reaction menu contents, review the current reactionOptions and quick/extended action model

Polls:

  • QuotedPoll was removed
  • Poll.isQuoted was removed
  • AddCommentForm, EndPollDialog, and SuggestPollOptionForm were renamed to AddCommentPrompt, EndPollAlert, and SuggestPollOptionPrompt
  • several poll-dialog override components no longer receive close callbacks directly and now depend on dialog context

If you previously rendered quoted polls with QuotedPoll or <Poll isQuoted />, move that UI to the quoted-message layer instead of the poll component itself. The default quoted preview already renders poll messages as a compact summary, so integrations that only need a small quoted-poll preview can usually switch to QuotedMessage or QuotedMessagePreviewUI:

import { QuotedMessagePreviewUI } from "stream-chat-react";

<QuotedMessagePreviewUI quotedMessage={quotedMessage} />;

If you need a richer quoted-poll card than the default compact preview, override QuotedMessage or QuotedMessagePreview and render quotedMessage.poll inside your own quoted-message UI.

Dialogs:

  • Modal was replaced by GlobalModal
  • ButtonWithSubmenu was removed; submenu-style UIs should be rebuilt around ContextMenu
  • the old .str-chat__modal__inner wrapper no longer exists
  • some prompt-like override components, such as MessageBouncePrompt, now close through modal context instead of an onClose prop
  • RecordingPermissionDeniedNotification no longer receives an onClose prop and no longer behaves like the old dismissible notification
  • custom dialog-style overrides that previously relied on explicit close callbacks should be reviewed for useModalContext()-based dismissal instead

Explicit Query Limits

Channel and ChannelList no longer inject the old initial query limits for you. If your app relied on the previous defaults, set them explicitly:

import { Channel, ChannelList } from "stream-chat-react";

<Channel channelQueryOptions={{ messages: { limit: 20 } }}>
  ...
</Channel>

<ChannelList options={{ limit: 30 }} />

Chat Sidebar State

Use initialNavOpen directly when you need an explicit initial sidebar state:

import { Chat } from "stream-chat-react";

<Chat initialNavOpen={false}>...</Chat>;

Header And Thread Changes

  • ChannelHeader.live was removed. The default header also no longer renders channel.data.subtitle.
  • ThreadHeader no longer accepts overrideImage.
  • ThreadHeader subtitle and close-button behavior changed.
  • in v13 the SDK only exposed the message-list typing indicator. In v14 typing state is split across two surfaces:
    • TypingIndicator is the message-list indicator. In v14 it renders an avatar stack plus animated dots, and it receives scrollToBottom plus optional isMessageListScrolledToBottom so it can stay pinned to the latest message while someone is typing.
    • TypingIndicatorHeader is the header subtitle indicator. ChannelHeader now switches its subtitle from online-status text to TypingIndicatorHeader when someone is typing in the channel, and ThreadHeader does the same for thread typing state.
  • custom TypingIndicator overrides only affect the message-list indicator. If you ship a custom ChannelHeader or ThreadHeader and want to preserve the new default typing behavior, render TypingIndicatorHeader in those custom headers yourself.
  • ChatView.ThreadAdapter no longer stays blank when no thread is selected. It now renders an empty-state placeholder after thread state is ready.
  • if you want to preserve the old blank thread-pane behavior, wire ThreadProvider manually or override EmptyStateIndicator in that scope with a component that renders null
  • ChatView.Selector now defaults to icon-only buttons. Pass iconOnly={false} to preserve the old labeled UI:

For example:

import { TypingIndicatorHeader } from "stream-chat-react";

const CustomChannelHeader = () => {
  return (
    <div>
      <div>General</div>
      <div>
        {hasTyping ? (
          <TypingIndicatorHeader />
        ) : (
          <span>{onlineStatusText}</span>
        )}
      </div>
    </div>
  );
};

const CustomThreadHeaderSubtitle = () => (
  <div>
    {hasThreadTyping ? (
      <TypingIndicatorHeader threadList />
    ) : (
      <span>
        {threadDisplayName} · {replyCount} replies
      </span>
    )}
  </div>
);

In the example above, hasTyping, onlineStatusText, hasThreadTyping, threadDisplayName, and replyCount come from your own header logic or the relevant SDK contexts/hooks.

import { ChatView } from "stream-chat-react";

<ChatView.Selector iconOnly={false} />;

Smaller Custom-Component Prop Changes To Review

If your app overrides low-level UI pieces, review these prop-surface changes as part of the migration:

Areav13v14
Avatar

image, name -> imageUrl, userName, required size
<Avatar image={url} name={name} /><Avatar imageUrl={url} size={32} userName={name} />
ChannelAvatar / GroupAvatar

groupChannelDisplayInfo -> displayMembers, overflowCount, required size
<ChannelAvatar groupChannelDisplayInfo={info} /><ChannelAvatar displayMembers={members} overflowCount={rest} size={40} />
channel display helpers

getDisplayTitle, getDisplayImage -> useChannelDisplayName(), getChannelDisplayImage()
getDisplayImage(channel)getChannelDisplayImage(channel)
TypingIndicatorProps

threadList? -> scrollToBottom, optional isMessageListScrolledToBottom, threadList
<TypingIndicator threadList /><TypingIndicator scrollToBottom={scrollToBottom} threadList />
ThreadHeaderProps

overrideImage, overrideTitle -> overrideTitle only
<ThreadHeader overrideImage={img} overrideTitle="Thread" /><ThreadHeader overrideTitle="Thread" />
MessageEditedTimestamp

removed in favor of MessageEditedIndicator
<MessageEditedTimestamp calendar open={open} /><MessageEditedIndicator calendar />
MessageStatusProps

injected Avatar removed
const MyStatus = ({ Avatar, ...props }) => ...const MyStatus = (props) => ...
MessageTextProps

theme removed
<MessageText message={message} theme={theme} /><MessageText message={message} />
PinIndicatorProps

t removed; read translations from context instead
({ message, t }) => t("Pinned")({ message }) => { const { t } = useTranslationContext(); ... }
suggestion UserItemProps

injected Avatar removed
({ entity, Avatar }) => <Avatar user={entity} />({ entity }) => <UserContextMenuButton userName={entity.name} />
FileIconProps

size and mode props replaced by fileName, mimeType, optional className
<FileIcon filename="a.pdf" size={32} /><FileIcon fileName="a.pdf" mimeType="application/pdf" />

Two smaller behavior changes are easy to miss during this pass:

  • useChannelPreviewInfo() now returns stable empty group info instead of null / undefined, so any “is this a group channel?” checks based on groupChannelDisplayInfo truthiness should be reviewed
  • useLatestMessagePreview() now reports native giphy attachments as type: "giphy", so custom channel previews should not assume all GIF-like content comes through the image branch
  • custom typing indicators should accept the new scroll-related props if they need to preserve the default “stay pinned to latest message while typing” behavior, and custom headers should render TypingIndicatorHeader if they want the new subtitle-level typing state

Styling, DOM, And Snapshot Review

v14 includes a visible markup refresh. If you maintain custom CSS, DOM queries, or visual snapshots, run a manual review over at least these areas:

  • channel header markup and sidebar toggle selectors
  • message composer wrappers and controls under str-chat__message-composer*
  • avatar markup, initials, and online-status badge selectors
  • channel preview and thread list item markup, including the move from aria-selected to aria-pressed
  • message reactions selector/detail markup
  • unread separator, new-message notification, and scroll-to-latest button markup
  • modal and dialog markup, especially removal of .str-chat__modal__inner
  • date separator visuals and any custom position / unread assumptions
  • attachment selector, link preview cards, and drag-and-drop overlay markup
  • loading skeletons and send-to-channel checkbox styling

If your theme relies on the old jump-to-latest CSS variables or old internal wrappers, expect to update those selectors. The default DateSeparator also no longer renders the old line-based position variants or the old unread-prefixed label, so provide a custom DateSeparator if your UI still depends on those visuals.