# 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 `ChannelList` and into `WithComponents` / `ComponentContext`.
4. Migrate message actions, edit flows, and custom message UIs to the v14 `MessageActions` and `MessageComposer` model.
5. Rename old `MessageInput*` imports to `MessageComposer*`, and update custom composer code to use `MessageComposerContext` plus the dedicated composer hooks.
6. Review attachment, gallery, reaction, poll, thread, and modal customizations for renamed props and components.
7. Remove `/v2` from any `stream-chat-react/dist/css/*` style imports and audit CSS selectors, snapshots, and DOM queries against the new markup.

## Import And Entrypoint Changes

The most common direct renames are:

| v13                                            | v14                                 |
| ---------------------------------------------- | ----------------------------------- |
| `MessageDeleted`                               | `MessageDeletedBubble`              |
| `MessageOptions`                               | `MessageActions`                    |
| `MessageInput`                                 | `MessageComposer`                   |
| `MessageInputFlat`                             | `MessageComposerUI`                 |
| `MessageInputContext`                          | `MessageComposerContext`            |
| `useMessageInputContext`                       | `useMessageComposerContext`         |
| `UploadButton`                                 | `FileInput`                         |
| `UploadButtonProps`                            | `FileInputProps`                    |
| `ChannelListMessenger`                         | `ChannelListUI`                     |
| `ChannelPreview`                               | `ChannelListItem`                   |
| `ChannelPreviewMessenger`                      | `ChannelListItemUI`                 |
| `ChannelPreviewActionButtons`                  | `ChannelListItemActionButtons`      |
| `ChannelSearch`                                | `Search`                            |
| `MessageNotification`                          | `NewMessageNotification`            |
| `ScrollToBottomButton`                         | `ScrollToLatestMessageButton`       |
| `ReactionsListModal`                           | `MessageReactionsDetail`            |
| `MessageIsThreadReplyInChannelButtonIndicator` | `MessageAlsoSentInChannelIndicator` |
| `Modal`                                        | `GlobalModal`                       |
| `AddCommentForm`                               | `AddCommentPrompt`                  |
| `EndPollDialog`                                | `EndPollAlert`                      |
| `SuggestPollOptionForm`                        | `SuggestPollOptionPrompt`           |

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 API                                                              | Alternative approach in v14                                                                                                                             |
| ------------------------------------------------------------------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `FixedHeightMessage`                                                     | provide a custom `VirtualMessage` built against the current `MessageUIComponentProps` contract                                                          |
| `EditMessageForm`, `EditMessageModal`                                    | start editing through `messageComposer.initState({ composition: message })`, clear with `messageComposer.clear()`, and customize `EditedMessagePreview` |
| `MessageActionsBox`, `MessageActionsWrapper`, `CustomMessageActionsList` | rebuild custom actions around `MessageActions`, `defaultMessageActionSet`, `messageActionSet`, and `ContextMenu`                                        |
| `QuotedPoll`, `Poll.isQuoted`                                            | move quoted-poll rendering to the quoted-message layer with `QuotedMessage`, `QuotedMessagePreview`, or `QuotedMessagePreviewUI`                        |
| `QuotedMessagePreviewHeader`                                             | customize the full quoted preview through `QuotedMessagePreview` / `QuotedMessagePreviewUI` instead of composing around a separate header               |
| `ButtonWithSubmenu`                                                      | rebuild 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:

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

Legacy stylesheet import paths also need cleanup. If your app still imports styles from `stream-chat-react/dist/css/v2/*`, switch to the root `dist/css/*` entrypoints instead:

```tsx
// v13 / older RC docs
import "stream-chat-react/dist/css/v2/index.css";

// v14
import "stream-chat-react/dist/css/index.css";
```

The last pre-stable cleanup also removed several deprecated aliases that were still hanging around from the v13 surface:

- remove `pinPermissions`, `PinPermissions`, `PinEnabledUserRoles`, and `defaultPinPermissions`
- update `usePinHandler(message, notifications?)` if you still passed the old permissions argument
- replace top-level `UploadButton` imports with `FileInput`
- replace `ChannelListItemUI.latestMessage` with `latestMessagePreview`
- replace `EmojiPicker.popperOptions` with `placement`
- update any `InfiniteScroll`, `LoadMoreButton`, or `LoadMorePaginator` wrappers from the old alias props to `hasNextPage`, `hasPreviousPage`, `loadNextPage`, `loadPreviousPage`, and `isLoading`
- remove imports of the old standalone channel-list listener hooks such as `useChannelDeletedListener`, `useNotificationMessageNewListener`, and `useMobileNavigation`

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

```tsx
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 `ActionsIcon`, `ReactionIcon`, `ThreadIcon`, `MessageErrorIcon`, `CloseIcon`, `SendIcon`, `MicIcon`, `MessageSentIcon`, and `MessageDeliveredIcon` with the public `Icons` set or higher-level components such as `MessageActions`, `SendButton`, and `MessageStatus`
- if you relied on `attachmentTypeIconMap`, inline your own map or migrate to the newer thread preview components

Examples:

Emoji-only checks:

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

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

Message action visibility:

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

const messageActionSet = defaultMessageActionSet.filter(
  ({ placement, type }) =>
    placement === "quick-dropdown-toggle" || type !== "delete",
);

<MessageActions messageActionSet={messageActionSet} />;
```

If you build a fully custom `messageActionSet` and still want dropdown actions, include a `quick-dropdown-toggle` item explicitly. The SDK no longer injects the menu trigger automatically.

Removed standalone icons:

```tsx
// v13
import { CloseIcon, MessageDeliveredIcon, SendIcon } from "stream-chat-react";
```

```tsx
// v14
import { IconXmark, MessageStatus, SendButton } from "stream-chat-react";
```

Replace removed low-level icons with the current shared icons or with higher-level components like `SendButton` and `MessageStatus`, depending on whether you are rebuilding UI primitives or using the default SDK flows.

Custom attachment icon maps:

```tsx
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:

```tsx
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:

```tsx
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`

`useChannelListContext()` also dropped its old diagnostic parameter. If you still call it as `useChannelListContext("MyComponent")`, remove that argument and do not rely on the old outside-provider warning side effect:

```tsx
import { useChannelListContext } from "stream-chat-react";

// v13
const { channels } = useChannelListContext("ChannelSidebar");

// v14
const { channels } = useChannelListContext();
```

## 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:

```tsx
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:

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

<WithComponents
  overrides={{
    MessageComposerUI: CustomMessageComposer,
    Message: CustomMessage,
    MessageActions: CustomMessageActions,
    Modal: CustomModal,
  }}
>
  <Channel>
    <Window>
      <ChannelHeader />
      <MessageList />
      <MessageComposer />
    </Window>
    <Thread />
  </Channel>
</WithComponents>;
```

Important override-key changes:

| v13 override key                               | v14 migration                                                                                    |
| ---------------------------------------------- | ------------------------------------------------------------------------------------------------ |
| `MessageNotification`                          | `NewMessageNotification`                                                                         |
| `ReactionsListModal`                           | `MessageReactionsDetail`                                                                         |
| `MessageOptions`                               | `MessageActions`                                                                                 |
| `MessageIsThreadReplyInChannelButtonIndicator` | `MessageAlsoSentInChannelIndicator`                                                              |
| `ChannelPreviewActionButtons`                  | `ChannelListItemActionButtons`                                                                   |
| `ChannelPreviewMessenger`                      | `ChannelListItemUI`                                                                              |
| `FileUploadIcon`                               | `AttachmentSelectorInitiationButtonContents`                                                     |
| `EditMessageInput`                             | remove old edit-flow override and customize `MessageComposerUI` / `EditedMessagePreview` instead |
| `EditMessageModal`                             | removed                                                                                          |
| `QuotedPoll`                                   | removed                                                                                          |

`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:

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

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

`ChannelList` also removed its direct UI override props. `List`, `Preview`, `Avatar`, `LoadingIndicator`, and `LoadingErrorIndicator` now come from `WithComponents` / `ComponentContext`.

| v13 `ChannelList` API   | v14 migration path                          |
| ----------------------- | ------------------------------------------- |
| `List`                  | `ChannelListUI` in `WithComponents`         |
| `Preview`               | `ChannelListItemUI` in `WithComponents`     |
| `Avatar`                | `Avatar` in `WithComponents`                |
| `LoadingIndicator`      | `LoadingIndicator` in `WithComponents`      |
| `LoadingErrorIndicator` | `LoadingErrorIndicator` in `WithComponents` |

Before:

```tsx
import {
  ChannelList,
  ChannelListMessenger,
  type ChannelListMessengerProps,
} from "stream-chat-react";

const CustomChannelList = (props: ChannelListMessengerProps) => (
  <ChannelListMessenger {...props} />
);

<ChannelList
  List={CustomChannelList}
  LoadingIndicator={CustomLoadingIndicator}
  Preview={CustomChannelListItem}
/>;
```

After:

```tsx
import {
  ChannelList,
  ChannelListItemUI,
  ChannelListUI,
  WithComponents,
} from "stream-chat-react";

const CustomChannelListUI = (props) => <ChannelListUI {...props} />;

const CustomChannelListItem = (props) => <ChannelListItemUI {...props} />;

<WithComponents
  overrides={{
    Avatar: CustomChannelAvatar,
    ChannelListItemUI: CustomChannelListItem,
    ChannelListUI: CustomChannelListUI,
    LoadingErrorIndicator: CustomLoadingErrorIndicator,
    LoadingIndicator: CustomLoadingIndicator,
  }}
>
  <ChannelList />
</WithComponents>;
```

If you rely on custom selectors, update them too:

- `.str-chat__channel-list-messenger` -> `.str-chat__channel-list-inner`
- `.str-chat__channel-list-messenger__main` -> `.str-chat__channel-list-inner__main`
- remove reliance on the deleted `.str-chat__channel-list-react` root class

## 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:

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

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

After:

```tsx
import {
  MessageActions,
  defaultMessageActionSet,
  useMessageContext,
  useMessageComposerController,
} from "stream-chat-react";

const CustomMessageActions = () => {
  const actions = defaultMessageActionSet.filter(
    ({ placement, type }) =>
      placement === "quick-dropdown-toggle" || type !== "delete",
  );
  return <MessageActions messageActionSet={actions} />;
};

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

  const onDelete = async () => {
    try {
      await handleDelete({ hardDelete: true });
    } catch {
      // The SDK already shows its default delete error notification.
    }
  };

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

Key message-level changes:

- `customMessageActions` was removed from `MessageContext`, `Message`, `MessageList`, and `VirtualizedMessageList`.
- `onlySenderCanEdit` was removed. Editability now follows channel capabilities; if you still need sender-only editing, filter `edit` out of your custom `messageActionSet` for messages the current user should not edit.
- `handleDelete` changed from `(event, options?)` to `(options?)`.
- deleting unsent or network-failed messages through `handleDelete()` now removes them locally instead of routing through the server delete path.
- `handleDelete()` now rethrows server-side delete failures after the SDK shows its default error notification, so custom delete buttons should use `try/catch` when they need app-specific recovery.
- 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.
- `MessageErrorText` and `MessageErrorIcon` were removed. The default failed-send badge now renders through the shared error indicator under `.str-chat__message-error-indicator`.
- `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`.
- the old message-notification callback props such as `getDeleteMessageErrorNotification` and `getMarkMessageUnreadSuccessNotification` were removed. Use notification translators, custom `MessageActions`, or `useNotificationApi()` when you need app-owned notifications.
- `ConnectionStatus` was removed. Replace it with an app-owned system banner built on `useSystemNotifications()` / `useReportLostConnectionSystemNotification()` and the [system notification banner cookbook](/chat/docs/sdk/react/guides/customization/system-notification-banner/).

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` was removed from the public surface
- client-side notifications now render through `NotificationList`
- `MessageList` and `VirtualizedMessageList` now render `NewMessageNotification`, `UnreadMessagesNotification`, and `ScrollToLatestMessageButton` directly
- if you previously wrapped the old notification container, move that layout work to `MessageListMainPanel` and the direct floating-notification override points
- `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
- the default `markUnread` action is now limited to foreign messages; it is no longer available on your own messages even when the channel has the required `read-events` capability

## Message Input And Composer Changes

The public composer surface was renamed from `MessageInput*` to `MessageComposer*`. Most composition state now lives in `MessageComposer`, `MessageComposerContext`, and the dedicated composer hooks.

Before:

```tsx
import { useMessageComposerContext } from "stream-chat-react";

const { cooldownRemaining, handleSubmit, setCooldownRemaining } =
  useMessageComposerContext();
```

After:

```tsx
import {
  useCooldownRemaining,
  useMessageComposerContext,
} from "stream-chat-react";

const CustomComposerFooter = () => {
  const { handleSubmit } = useMessageComposerContext();
  const cooldownRemaining = useCooldownRemaining();
  const isCooldownActive = cooldownRemaining > 0;

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

Key composer changes:

- `MessageInput`, `MessageInputFlat`, `MessageInputContext`, and `useMessageInputContext` were renamed to `MessageComposer`, `MessageComposerUI`, `MessageComposerContext`, and `useMessageComposerContext`.
- `additionalMessageInputProps` became `additionalMessageComposerProps`.
- `handleSubmit` now accepts only an optional event. It no longer accepts `customMessageData` or `SendMessageOptions`.
- `useMessageComposerBindings()` replaced the older low-level bindings hook name if you build custom composer providers.
- The default `CooldownTimer` is now a zero-prop component:

```tsx
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`.
- if you use the SDK `EmojiPicker`, import `stream-chat-react/dist/css/emoji-picker.css` in addition to the main chat stylesheet
- `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.

## Attachments, Gallery, And File/Media Changes

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:

```tsx
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:

```tsx
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:

| v13                                | v14                         |
| ---------------------------------- | --------------------------- |
| `filename`                         | `fileName`                  |
| `big`, `size`, `sizeSmall`, `type` | removed                     |
| `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`
- `sortReactionDetails` and `ReactionDetailsComparator` were removed. Use `reactionDetailsSort` with a server-side `ReactionSort` object instead.
- custom `MessageReactions` components should use `reaction_groups`, not `reaction_counts`
- `MessageReactions` no longer accepts direct `reactionOptions`; keep reaction-option customization in `WithComponents` / `ComponentContext`
- 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`:

```tsx
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:

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

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

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

### Sidebar State Is Now App-Owned

`Chat` no longer owns sidebar open/closed state. The old `initialNavOpen` prop and the `navOpen`, `openMobileNav`, and `closeMobileNav` values from `useChatContext()` were removed.

Keep that state in your app and inject any toggle UI through `HeaderStartContent` or `HeaderEndContent`:

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

const AppLayout = () => {
  const [sidebarOpen, setSidebarOpen] = useState(false);

  const SidebarToggle = () => (
    <button onClick={() => setSidebarOpen((open) => !open)} type="button">
      {sidebarOpen ? "Close sidebar" : "Open sidebar"}
    </button>
  );

  return (
    <WithComponents overrides={{ HeaderStartContent: SidebarToggle }}>
      <Channel channel={channel}>
        <Window>
          <ChannelHeader />
          <MessageList />
          <MessageComposer />
        </Window>
      </Channel>
    </WithComponents>
  );
};
```

The same change affects `ChannelHeader`: the old `MenuIcon` prop was removed, so any built-in sidebar toggle customization should move to `HeaderStartContent`.

For a complete app-owned implementation, see the [Collapsible Sidebar cookbook](/chat/docs/sdk/react/guides/customization/collapsible-sidebar/).

### 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:

```tsx
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.

```tsx
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:

| Area                                                                                                                             | v13                                                           | v14                                                                |
| -------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------- | ------------------------------------------------------------------ |
| `Avatar`<br /><br />`image`, `name` -> `imageUrl`, `userName`, required `size`                                                   | `<Avatar image={url} name={name} />`                          | `<Avatar imageUrl={url} size={32} userName={name} />`              |
| `ChannelAvatar` / `GroupAvatar`<br /><br />`groupChannelDisplayInfo` -> `displayMembers`, required `size`, automatic overflow    | `<ChannelAvatar groupChannelDisplayInfo={info} />`            | `<ChannelAvatar displayMembers={members} size={40} />`             |
| channel display helpers<br /><br />`getDisplayTitle`, `getDisplayImage` -> `useChannelDisplayName()`, `getChannelDisplayImage()` | `getDisplayImage(channel)`                                    | `getChannelDisplayImage(channel)`                                  |
| `TypingIndicatorProps`<br /><br />`threadList?` -> `scrollToBottom`, optional `isMessageListScrolledToBottom`, `threadList`      | `<TypingIndicator threadList />`                              | `<TypingIndicator scrollToBottom={scrollToBottom} threadList />`   |
| `ThreadHeaderProps`<br /><br />`overrideImage`, `overrideTitle` -> `overrideTitle` only                                          | `<ThreadHeader overrideImage={img} overrideTitle="Thread" />` | `<ThreadHeader overrideTitle="Thread" />`                          |
| `MessageEditedTimestamp`<br /><br />removed in favor of `MessageEditedIndicator`                                                 | `<MessageEditedTimestamp calendar open={open} />`             | `<MessageEditedIndicator calendar />`                              |
| `MessageStatusProps`<br /><br />injected `Avatar` removed                                                                        | `const MyStatus = ({ Avatar, ...props }) => ...`              | `const MyStatus = (props) => ...`                                  |
| `MessageTextProps`<br /><br />`theme` removed                                                                                    | `<MessageText message={message} theme={theme} />`             | `<MessageText message={message} />`                                |
| `PinIndicatorProps`<br /><br />`t` removed; read translations from context instead                                               | `({ message, t }) => t("Pinned")`                             | `({ message }) => { const { t } = useTranslationContext(); ... }`  |
| suggestion `UserItemProps`<br /><br />injected `Avatar` removed                                                                  | `({ entity, Avatar }) => <Avatar user={entity} />`            | `({ entity }) => <UserContextMenuButton userName={entity.name} />` |
| `FileIconProps`<br /><br />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.


---

This page was last updated at 2026-04-17T17:33:52.013Z.

For the most recent version of this documentation, visit [https://getstream.io/chat/docs/sdk/react/release-guides/upgrade-to-v14/](https://getstream.io/chat/docs/sdk/react/release-guides/upgrade-to-v14/).