# Overriding components

> **Info:** This is release candidate documentation for **Stream Chat React SDK v15**. For the latest stable version, see the [latest version (v14)](https://getstream.io/chat/docs/sdk/react/).

Every replaceable part of the UI has a key in `ComponentContext`. When an SDK component renders a part, it looks up the part's key and renders your component if you set one, or the SDK's own otherwise. `WithComponents` sets keys for everything below it.

For example, this replaces the date separator in every message list, and the send icon everywhere it appears:

```tsx
import type { DateSeparatorProps } from "stream-chat-react";
import { Chat, createIcon, WithComponents } from "stream-chat-react";
import type { PropsWithChildren } from "react";

const PlainDateSeparator = ({ date }: DateSeparatorProps) => (
  <div className="str-chat__date-separator">{date.toLocaleDateString()}</div>
);

const PaperPlaneIcon = createIcon(
  "IconSend",
  <path
    d="M3 10l14-7-5 14-2-6-7-1z"
    fill="none"
    stroke="currentColor"
    strokeWidth="1.5"
  />,
);

// Declared once, so every render passes the same object.
const overrides = {
  DateSeparator: PlainDateSeparator,
  icons: { IconSend: PaperPlaneIcon },
};

export const App = ({ children }: PropsWithChildren) => (
  <WithComponents overrides={overrides}>
    <Chat client={client}>{children}</Chat>
  </WithComponents>
);
```

1. `MessageList` renders a date separator between days. It reads the `DateSeparator` key and finds `PlainDateSeparator`.
2. The send button reads its icon through `useComponentContextIcons()` and finds `PaperPlaneIcon` under `IconSend`.
3. Every other key keeps the SDK's component.

## WithComponents vs ComponentProvider

Both set `ComponentContext` for their subtree. They differ in what happens to the keys an outer provider set:

- **`WithComponents` merges.** The keys in `overrides` replace the same keys from any `WithComponents` above it, and every other key keeps its outer value. The `icons` key merges icon by icon, so overriding one icon keeps the icons an outer provider set.
- **`ComponentProvider` replaces.** Its `value` becomes the whole context: a key it doesn't set falls back to the SDK's component, even if an outer provider set it. Use it to reset a subtree to the defaults.

Nesting `WithComponents` scopes an override to part of the app:

```tsx
import {
  Channel,
  MessageComposer,
  MessageList,
  WithComponents,
} from "stream-chat-react";
import type { DateSeparatorProps } from "stream-chat-react";
import type { Channel as StreamChannel } from "stream-chat";

const DayOnly = ({ date }: DateSeparatorProps) => (
  <div className="str-chat__date-separator">{date.toLocaleDateString()}</div>
);

const SupportComposerUI = () => (
  <div className="support-composer">Replies are closed.</div>
);

const supportOverrides = { MessageComposerUI: SupportComposerUI };
const appOverrides = { DateSeparator: DayOnly };

export const SupportChannel = ({ channel }: { channel: StreamChannel }) => (
  <WithComponents overrides={appOverrides}>
    {/* This channel keeps DayOnly and replaces the composer UI too. */}
    <WithComponents overrides={supportOverrides}>
      <Channel channel={channel}>
        <MessageList />
        <MessageComposer />
      </Channel>
    </WithComponents>
  </WithComponents>
);
```

Where to put it:

- **Above the component that reads the key.** `Chat` itself reads `NotificationAnnouncer`, so that key only takes effect from a `WithComponents` around `Chat`. For app-wide overrides, wrap `Chat`.
- **With a stable `overrides` object.** A new object on every render gives every component that reads the context a new value, and they all re-render. Declare it at module scope, as above, or build it with `useMemo`.

## Override keys by area

The props each key's component receives, and its default, are on [ComponentContext](https://getstream.io/chat/docs/sdk/react/v15/component-reference/channel/componentcontext/). The Components pages say which part of the screen each key draws.

- **[Message list](https://getstream.io/chat/docs/sdk/react/v15/ui/components/message-list-ui/)**: `DateSeparator`, `EmptyStateIndicator`, `GiphyPreviewMessage`, `HeaderComponent`, `LoadingIndicator`, `MessageListItem`, `MessageListMainPanel`, `MessageListWrapper`, `MessageSystem`, `NewMessageNotification`, `ThreadHead`, `ThreadStart`, `TypingIndicator`, `UnreadMessagesNotification`, `UnreadMessagesSeparator`.
- **[A message](https://getstream.io/chat/docs/sdk/react/v15/ui/components/message-list-ui/)**: `MessageUI`, `VirtualMessage`, `MessageActions`, `MessageAlsoSentInChannelIndicator`, `MessageBlocked`, `MessageBouncePrompt`, `MessageDeleted`, `MessageDeletedBubble`, `MessageEditedIndicator`, `MessageRepliesCountButton`, `MessageStatus`, `MessageTimestamp`, `MessageTranslationIndicator`, `PinIndicator`, `QuotedMessage`, `ReminderNotification`, `StreamedMessageText`, `Timestamp`, `Avatar`, `AvatarStack`, `extractDisplayInfo`.
- **[Reactions](https://getstream.io/chat/docs/sdk/react/v15/features/messaging/reactions/)**: `ReactionSelector`, `ReactionSelectorExtendedList`, `MessageReactions`, `MessageReactionsDetail`, `reactionOptions`.
- **[Attachments and media](https://getstream.io/chat/docs/sdk/react/v15/features/rich-content/attachments-and-uploads/)**: `Attachment`, `AttachmentFileIcon`, `AttachmentUploadProgressIndicator`, `BaseImage`, `FileSizeIndicator`, `Gallery`, `GalleryUI`, `ImagePlaceholder`, `ModalGallery`, `ProgressIndicator`, `UploadedSizeIndicator`, `VideoPlayer`.
- **[Polls](https://getstream.io/chat/docs/sdk/react/v15/features/rich-content/polls/)**: `PollActions`, `PollContent`, `PollCreationDialog`, `PollHeader`, `PollOptionSelector`.
- **[Message composer](https://getstream.io/chat/docs/sdk/react/v15/ui/components/message-composer-ui/)**: `MessageComposerUI`, `TextareaComposer`, `AutocompleteSuggestionItem`, `AutocompleteSuggestionList`, `AttachmentSelector`, `AttachmentSelectorInitiationButtonContents`, `AttachmentPreviewList`, `VoiceRecordingPreviewSlot`, `AdditionalMessageComposerActions`, `AudioRecorder`, `StartRecordingAudioButton`, `RecordingPermissionDeniedNotification`, `CommandChip`, `CooldownTimer`, `EditedMessagePreview`, `QuotedMessagePreview`, `LinkPreviewList`, `SendButton`, `SendToChannelCheckbox`, `StopAIGenerationButton`, `EmojiPicker`, `emojiSearchIndex`, `ShareLocationDialog`.
- **[Channel list](https://getstream.io/chat/docs/sdk/react/v15/ui/components/channel-list-ui/)**: `ChannelListHeader`, `ChannelListItemUI`, `ChannelListItemActionButtons`, `SummarizedMessagePreview`, `HeaderEndContent`, and for its pages: `EmptyListIndicator`, `EndReachedIndicator`, `FirstPageLoadingIndicator`, `ListItem`, `LoadingNextPageIndicator`.
- **[Search](https://getstream.io/chat/docs/sdk/react/v15/features/channels/search/)**: `Search`, `SearchBar`, `SearchResults`, `SearchResultsHeader`, `SearchResultsPresearch`, `SearchSourceResultList`, `SearchSourceResultListFooter`, `SearchSourceResults`, `SearchSourceResultsEmpty`, `SearchSourceResultsHeader`, `SearchSourceResultsLoadingIndicator`.
- **[Thread list](https://getstream.io/chat/docs/sdk/react/v15/ui/components/thread-ui/)**: `ThreadListEmptyPlaceholder`, `ThreadListItem`, `ThreadListItemUI`, `ThreadListLoadingIndicator`, `ThreadListUnseenThreadsBanner`.
- **[Notifications](https://getstream.io/chat/docs/sdk/react/v15/ui/customizing/displaying-notifications/)**: `Notification`, `NotificationAnnouncer`, `NotificationList`.
- **[Dialogs and menus](https://getstream.io/chat/docs/sdk/react/v15/ui/customizing/dialogs/)**: `CalloutDialog`, `ContextMenu`, `ContextMenuContent`, `Modal`.
- **[Icons](#icons)**: `icons`.

`extractDisplayInfo`, `emojiSearchIndex` and `reactionOptions` take a function or data, not a component.

The `ComponentContext` type also lists `FileDragAndDropContent` and `ScrollToLatestMessageButton`, but no component reads them, so setting them changes nothing.

`ChannelHeader`, `ThreadHeader`, `MessageList` and `MessageComposer` have no key: you render them yourself, so you change them by rendering something else. See [Composition](#composition).

## Icons

Every icon the SDK renders, from the send arrow and the delivery checkmarks to the mute and pin badges and the attachment selector's glyphs, comes from the `icons` key. Give it only the icons you want to change: the others keep their defaults.

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

const SlashedBellIcon = createIcon(
  "IconMute",
  <path
    d="M4 4l12 12M8 16h4M6 8a4 4 0 017.5-2M14 10v2l1.5 2H7"
    fill="none"
    stroke="currentColor"
    strokeLinecap="round"
    strokeWidth="1.5"
  />,
);

const iconOverrides = { icons: { IconMute: SlashedBellIcon } };

export const ChannelListWithIcons = () => (
  <WithComponents overrides={iconOverrides}>
    <ChannelList paginator={paginator} />
  </WithComponents>
);
```

The mute icon in that subtree, for example in a channel-list row and its actions menu, is now `SlashedBellIcon`. The names are those of the icon components `stream-chat-react` exports, such as `IconSend`, `IconReply`, `IconPin` and `IconMute`, and the `IconName` type lists every one. File-type icons on attachments are a separate key, `AttachmentFileIcon`.

### What an icon receives

An icon is a component of type `IconComponent`. It receives the props of an `<svg>` element, plus `decorative`:

- **`className`.** The SDK passes classes such as `str-chat__icon--destructive`. Keep them.
- **`decorative`.** `true` by default: the icon is hidden from screen readers (`aria-hidden`). Pass `false` when the icon is the only label of a control.
- **The rest of the SVG props,** such as `aria-label` or `style`.

The SDK's own icons have a `0 0 20 20` view box, mostly draw with `currentColor`, and carry the `str-chat__icon` class and a class named after the icon, such as `str-chat__icon--mute`. Stylesheets and theming variables size and color icons through those classes.

### Building an icon

`createIcon(name, content)` builds an icon the way the SDK builds its own: it wraps `content` in an `<svg>` with the view box, the classes and the accessibility attributes above, and forwards the props it receives. `content` is the inside of the SVG, drawn for a 20 by 20 box.

An icon doesn't have to be an SVG. Any component of type `IconComponent` works, for example an image or an icon from another library:

```tsx
import clsx from "clsx";
import type { IconComponent } from "stream-chat-react";

export const LogoSendIcon: IconComponent = ({
  className,
  decorative = true,
}) => (
  <img
    alt=""
    aria-hidden={decorative}
    className={clsx("str-chat__icon", className)}
    src="/logo-send.svg"
  />
);
```

Keep `className` and the `str-chat__icon` class, so the SDK's stylesheet still sizes the icon. Keep icons light: one may render once per message.

### Inheritance

Icon overrides merge icon by icon across nested `WithComponents`. Set app-wide icons around `Chat`, and override one icon deeper down where a part of the app needs it:

```tsx
import {
  Channel,
  Chat,
  createIcon,
  MessageComposer,
  MessageList,
  WithComponents,
} from "stream-chat-react";
import type { Channel as StreamChannel } from "stream-chat";

const BrandSendIcon = createIcon(
  "IconSend",
  <circle cx="10" cy="10" r="7" fill="currentColor" />,
);
const UrgentSendIcon = createIcon(
  "IconSend",
  <rect x="3" y="3" width="14" height="14" fill="currentColor" />,
);

const brandIcons = { icons: { IconSend: BrandSendIcon } };
const urgentIcons = { icons: { IconSend: UrgentSendIcon } };

export const App = ({ escalations }: { escalations: StreamChannel }) => (
  <WithComponents overrides={brandIcons}>
    <Chat client={client}>
      {/* This channel uses UrgentSendIcon; every other icon is inherited. */}
      <WithComponents overrides={urgentIcons}>
        <Channel channel={escalations}>
          <MessageList />
          <MessageComposer />
        </Channel>
      </WithComponents>
    </Chat>
  </WithComponents>
);
```

### Reading the icons in your own components

`useComponentContextIcons()` returns every icon, with the overrides in effect merged over the defaults. A custom component that uses it shows the same icons as the SDK's:

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

export const ShareButton = ({ onClick }: { onClick: () => void }) => {
  const { IconArrowUpRight } = useComponentContextIcons();

  return (
    <button aria-label="Share" onClick={onClick} type="button">
      <IconArrowUpRight />
    </button>
  );
};
```

## A custom MessageUI

`MessageUI` renders each message in `MessageList` and `VirtualizedMessageList`. `VirtualMessage`, when set, replaces it in `VirtualizedMessageList` only.

The SDK passes a custom `MessageUI` **no props**. `Message` puts everything about the message into `MessageContext`, and the component reads it with `useMessageContext()`. The parts the default `MessageUI` is built from read the same context, so a custom one can compose them:

```tsx
import {
  Attachment,
  isMessageDeleted,
  MessageActions,
  MessageText,
  MessageTimestamp,
  useMessageContext,
  WithComponents,
} from "stream-chat-react";
import type { PropsWithChildren } from "react";

const CompactMessage = () => {
  const { isMyMessage, message } = useMessageContext();

  if (isMessageDeleted(message)) {
    return (
      <div className="compact-message compact-message--deleted">
        Message deleted
      </div>
    );
  }

  return (
    <div
      className={
        isMyMessage()
          ? "compact-message compact-message--mine"
          : "compact-message"
      }
    >
      <strong>{message.user?.name ?? message.user?.id}</strong>
      <MessageText />
      {message.attachments?.length ? (
        <Attachment attachments={message.attachments} />
      ) : null}
      <MessageTimestamp />
      <MessageActions />
    </div>
  );
};

const messageOverrides = { MessageUI: CompactMessage };

export const CompactMessages = ({ children }: PropsWithChildren) => (
  <WithComponents overrides={messageOverrides}>{children}</WithComponents>
);
```

`MessageUI` renders every regular message: deleted ones, failed ones, ones with a poll or a quoted message. The default handles all of them. A custom one handles the cases your app has, and system messages go to `MessageSystem` instead.

The message parts and their props are on [Message UI components](https://getstream.io/chat/docs/sdk/react/v15/component-reference/message/message-ui-components/), and a full walkthrough is the [Custom message](https://getstream.io/chat/docs/sdk/react/v15/recipes/message/custom-message/) recipe.

## Composition

Not every change needs a key. `Channel`, `Thread` and the layout's slots render no UI of their own, only the children you give them, so their content is yours to compose: leave a part out, add your own, or change the order.

```tsx
import {
  Channel,
  ChannelHeader,
  MessageComposer,
  MessageList,
} from "stream-chat-react";
import type { Channel as StreamChannel } from "stream-chat";

const RulesBanner = () => (
  <div className="rules-banner">Be kind. Moderators are watching.</div>
);

export const ModeratedChannel = ({ channel }: { channel: StreamChannel }) => (
  <Channel channel={channel}>
    <ChannelHeader />
    <RulesBanner />
    <MessageList />
    <MessageComposer />
  </Channel>
);
```

### Header content

`ChannelHeader` and `ThreadHeader` take two slots:

- **`StartContent`**, at the start. It defaults to `WorkspaceNavigationBackButton`, the back button of a panel opened on top of another.
- **`EndContent`**, at the end. It defaults to `WorkspaceNavigationCloseButton`, the close button of a panel that can be closed.

A component you pass replaces the default. To keep the default button, render it yourself:

```tsx
import {
  ChannelHeader,
  useChannel,
  WorkspaceNavigationCloseButton,
} from "stream-chat-react";

const HeaderEnd = () => {
  const channel = useChannel();

  return (
    <>
      <a href={`/calls/${channel.id}`} rel="noreferrer" target="_blank">
        Call
      </a>
      <WorkspaceNavigationCloseButton />
    </>
  );
};

export const CallableChannelHeader = () => (
  <ChannelHeader EndContent={HeaderEnd} />
);
```

`ChannelHeader` also takes `Avatar`, `title` and `image`. The headers of the channel list and the thread list have a key instead: `HeaderEndContent`, rendered at the end of the channel list's header while a channel is open, and of the thread list's while a thread is open.

The [Channel header](https://getstream.io/chat/docs/sdk/react/v15/recipes/headers/channel-header/) and [Thread header](https://getstream.io/chat/docs/sdk/react/v15/recipes/headers/thread-header/) recipes build complete headers.

## Pitfalls

- **An `overrides` object created on every render.** It re-renders every component that reads `ComponentContext`, including each message. Declare it once.
- **`ComponentProvider` where you meant `WithComponents`.** It drops the overrides of every provider above it.
- **An override below `Chat` for a key `Chat` reads.** `NotificationAnnouncer` takes effect only from above `Chat`.
- **A custom `MessageUI` that expects props.** It receives none: read `useMessageContext()`.
- **`VirtualMessage` set, and a `MessageUI` change that doesn't show.** In `VirtualizedMessageList`, `VirtualMessage` wins.
- **An `AttachmentSelector` override in the edit composer.** While a message is being edited, the SDK uses a simpler selector that only uploads files. A component set on the `AttachmentSelector` key replaces both.

Used in: most Features pages and every recipe, for example [Custom message](https://getstream.io/chat/docs/sdk/react/v15/recipes/message/custom-message/) and [Custom composer](https://getstream.io/chat/docs/sdk/react/v15/recipes/composer/custom-composer/).

---

For the most recent version of this documentation, visit [https://getstream.io/chat/docs/sdk/react/v15/ui/customizing/overriding-components/](https://getstream.io/chat/docs/sdk/react/v15/ui/customizing/overriding-components/).