# Overriding components

Most parts of the SDK's UI can be replaced with a component of your own. Use this page to pick the right way for the part you want to change, and to see how each way works.

| To change                                                                                                               | Do this                                                                                                                                | Pattern                                                                 |
| ----------------------------------------------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------- | ----------------------------------------------------------------------- |
| **What the SDK renders**                                                                                                |                                                                                                                                        |                                                                         |
| A component, such as the date separator, each message, the send button or the typing indicator                          | Register your component under its key                                                                                                  | [Override with `<WithComponents/>`](#how-withcomponents-overrides-work) |
| An icon                                                                                                                 | Register it under the `icons` key. See [Icons](#icons).                                                                                | [Override with `<WithComponents/>`](#how-withcomponents-overrides-work) |
| Something the SDK looks up that isn't a component: the reactions users can pick, or the name and image shown for a user | Register a value under `reactionOptions`, or a function under `extractDisplayInfo`. See [Values and functions](#values-and-functions). | [Override with `<WithComponents/>`](#how-withcomponents-overrides-work) |
| **What you render**                                                                                                     |                                                                                                                                        |                                                                         |
| What a `<Channel/>`, a `<Thread/>` or a layout slot shows                                                               | Render other children inside it                                                                                                        | [Compose children](#composition)                                        |
| The props of a component you render, such as a header's edge buttons or the composer's voice recording                  | Pass other props                                                                                                                       | [Pass props](#component-props)                                          |

## How WithComponents overrides work

Every replaceable part 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.

Here is an `App` component that 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";

// The list finds date separators by the class and reads their dates from `data-date`.
const PlainDateSeparator = ({ date, floating }: DateSeparatorProps) => (
  <div
    className={`str-chat__date-separator${floating ? " str-chat__date-separator--floating" : ""}`}
    data-date={date.toISOString()}
  >
    {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>
);
```

What happens when the app renders:

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.

### Where to put an override

| Rule                                                           | Why                                                                                                                                                                                           |
| -------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| Put `<WithComponents/>` above the component that reads the key | A key takes effect only below it. `<Chat/>` itself reads `NotificationAnnouncer`, so that key works only from a `<WithComponents/>` around `<Chat/>`. For app-wide overrides, wrap `<Chat/>`. |
| Keep stable reference to `overrides` object                    | A new object on every render gives every component that reads the context a new value, and they all re-render, each message included. Declare it at module scope, or build it with `useMemo`. |

### WithComponents and ComponentProvider

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

|                    | `<WithComponents overrides={...}>`                            | `<ComponentProvider value={...}>`              |
| ------------------ | ------------------------------------------------------------- | ---------------------------------------------- |
| Keys you set       | Replace the same keys from the providers above                | Replace the same keys from the providers above |
| Keys you don't set | Keep the value from the providers above                       | Fall back to the SDK's component               |
| `icons`            | Merged icon by icon: the icons you don't set keep their value | Replaced as a whole                            |
| Use it to          | Override parts                                                | Reset a subtree to the SDK's components        |

Nesting `<WithComponents/>` scopes an override to part of the app. Here is a `SupportChannel` component that keeps the app's date separator and also replaces the composer UI in one channel:

```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";

// The list finds date separators by the class and reads their dates from `data-date`.
const DayOnly = ({ date, floating }: DateSeparatorProps) => (
  <div
    className={`str-chat__date-separator${floating ? " str-chat__date-separator--floating" : ""}`}
    data-date={date.toISOString()}
  >
    {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>
);
```

### A custom MessageUI

The `MessageUI` key sets the component that renders each message in `<MessageList/>` and `<VirtualizedMessageList/>`. The `VirtualMessage` key, 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. Here is a `CompactMessages` component that renders every message in a compact form:

```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 overrides = { MessageUI: CompactMessage };

export const CompactMessages = ({ children }: PropsWithChildren) => (
  <WithComponents overrides={overrides}>{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. System messages go to `MessageSystem` instead.

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

## Override keys by area

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

| Area                                                                                         | Keys                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                    |
| -------------------------------------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| [Message list](https://getstream.io/chat/docs/sdk/react/ui/components/message-list-ui/)                          | `DateSeparator`, `EmptyStateIndicator`, `GiphyPreviewMessage`, `HeaderComponent`, `LoadingIndicator`, `MessageListItem`, `MessageListMainPanel`, `MessageListWrapper`, `MessageSystem`, `NewMessageNotification`, `ScrollToLatestMessageButton`, `ThreadHead`, `ThreadStart`, `TypingIndicator`, `UnreadMessagesNotification`, `UnreadMessagesSeparator`                                                                                                                                                                                                                                                                |
| [A message](https://getstream.io/chat/docs/sdk/react/ui/components/message-ui/)                                  | `MessageUI`, `VirtualMessage`, `MessageActions`, `MessageAlsoSentInChannelIndicator`, `MessageBlocked`, `MessageBouncePrompt`, `MessageBubbleContent`, `MessageDeleted`, `MessageDeletedBubble`, `MessageEditedIndicator`, `MessageRepliesCountButton`, `MessageStatus`, `MessageText`, `MessageTimestamp`, `MessageTranslationIndicator`, `PinIndicator`, `QuotedMessage`, `ReminderNotification`, `StreamedMessageText`, `Timestamp`, `Avatar`, `AvatarStack`, `extractDisplayInfo`                                                                                                                                   |
| [Reactions](https://getstream.io/chat/docs/sdk/react/features/messaging/reactions/)                              | `ReactionSelector`, `ReactionSelectorExtendedList`, `MessageReactions`, `MessageReactionsDetail`, `reactionOptions`                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                     |
| [Attachments and media](https://getstream.io/chat/docs/sdk/react/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/features/rich-content/polls/)                                   | `PollActions`, `PollContent`, `PollCreationDialog`, `PollCreationDialogControls`, `PollHeader`, `PollOptionSelector`                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                    |
| [Message composer](https://getstream.io/chat/docs/sdk/react/ui/components/message-composer-ui/)                  | `MessageComposerUI`, `MessageComposerPreviews`, `TextareaComposer`, `AutocompleteSuggestionItem`, `AutocompleteSuggestionList`, `AttachmentSelector`, `AttachmentSelectorInitiationButtonContents`, `AttachmentPreviewList`, `VoiceRecordingPreviewSlot`, `AdditionalMessageComposerActions`, `AudioRecorder`, `StartRecordingAudioButton`, `RecordingPermissionDeniedNotification`, `CommandChip`, `CooldownTimer`, `EditedMessagePreview`, `QuotedMessagePreview`, `LinkPreviewList`, `SendButton`, `SendToChannelCheckbox`, `StopAIGenerationButton`, `EmojiPicker`, `FileDragAndDropContent`, `ShareLocationDialog` |
| [Channel list](https://getstream.io/chat/docs/sdk/react/ui/components/channel-list-ui/)                          | `ChannelListHeader`, `ChannelListItemUI`, `ChannelListItemActionButtons`, `SummarizedMessagePreview`, and for its pages: `EmptyListIndicator`, `EndReachedIndicator`, `ListItem`, `LoadingNextPageIndicator`. Shared with the thread list: `FirstPageLoadingIndicator`, `ListLoadErrorIndicator` ([replacing them in one list only](https://getstream.io/chat/docs/sdk/react/ui/components/list-states-ui/#replacing-a-state-in-one-list-only))                                                                                                                                                                                             |
| [Search](https://getstream.io/chat/docs/sdk/react/features/channels/search/)                                     | `Search`, `SearchBar`, `SearchResults`, `SearchResultsHeader`, `SearchResultsPresearch`, `SearchSourceResultList`, `SearchSourceResultListFooter`, `SearchSourceResults`, `SearchSourceResultsEmpty`, `SearchSourceResultsHeader`, `SearchSourceResultsLoadingIndicator`                                                                                                                                                                                                                                                                                                                                                |
| [Thread list](https://getstream.io/chat/docs/sdk/react/ui/components/thread-list-ui/)                            | `ThreadListEmptyPlaceholder`, `ThreadListHeader`, `ThreadListItem`, `ThreadListItemUI`, `ThreadListLoadingIndicator`, `ThreadListUnseenThreadsBanner`. Shared with the channel list: `FirstPageLoadingIndicator`, `ListLoadErrorIndicator` ([replacing them in one list only](https://getstream.io/chat/docs/sdk/react/ui/components/list-states-ui/#replacing-a-state-in-one-list-only))                                                                                                                                                                                                                                                   |
| [Notifications](https://getstream.io/chat/docs/sdk/react/ui/components/notifications-ui/)                        | `Notification`, `NotificationAnnouncer`, `NotificationList`                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                             |
| [Dialogs and menus](https://getstream.io/chat/docs/sdk/react/ui/components/dialogs/)                             | `CalloutDialog`, `ContextMenu`, `ContextMenuContent`, `Modal`                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                           |
| [Icons](#icons)                                                                              | `icons`                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                 |

### Values and functions

Besides `icons`, two keys take a value or a function instead of a component:

| Key                  | Takes                                                               | Read by                                                                                                                                                                                                                                 |
| -------------------- | ------------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `reactionOptions`    | The reactions users can pick, and how each one renders              | The reaction selector, the reactions on a message and the reactions detail                                                                                                                                                              |
| `extractDisplayInfo` | A function that turns a user into the name and image shown for them | Most avatars of a user: message authors, the reactions detail, poll votes, mention suggestions, search results, the typing indicator, thread participants and the channel details lists. The channel list's group avatars don't read it |

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

Here is a `ChannelListWithIcons` component that replaces the mute icon:

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

| Prop                      | What to do with it                                                                                                                    |
| ------------------------- | ------------------------------------------------------------------------------------------------------------------------------------- |
| `className`               | Keep it. The SDK passes classes such as `str-chat__icon--destructive`.                                                                |
| `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 | Pass them on, 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 in [What an icon receives](#what-an-icon-receives), 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. Here is a `LogoSendIcon` component that shows an image:

```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. Here is an `App` component with a brand send icon everywhere and another one in an escalations channel:

```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 component that uses it shows the same icons as the SDK's. Here is a `ShareButton` component that uses the SDK's arrow icon:

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

## Composition

Not every change needs a key in `ComponentContext`. `<Channel/>`, `<Thread/>` and the layout's slots render no UI of their own, only the children you give them, so you choose what they show: leave a part out, add your own, or change the order. Here is a `ModeratedChannel` component that adds a rules banner under the header:

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

## Component props

The components you render take props that change what they show. Each component's page in the Component reference lists its props, for example [MessageList](https://getstream.io/chat/docs/sdk/react/component-reference/message-list/messagelist/) and [MessageComposer](https://getstream.io/chat/docs/sdk/react/component-reference/message-composer/messagecomposer/). For the headers, [The header](https://getstream.io/chat/docs/sdk/react/ui/components/channel-ui/#the-header) in Channel UI and [The thread header](https://getstream.io/chat/docs/sdk/react/ui/components/thread-ui/#the-thread-header) in Thread UI show which part each prop replaces.

## Pitfalls

| Symptom                                                                                                        | Cause                                                                                                                                                                                                            | Fix                                                                                                                                                                                                       |
| -------------------------------------------------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| Every message re-renders on each render of your app                                                            | An `overrides` object created on every render                                                                                                                                                                    | Keep stable reference                                                                                                                                                                                     |
| Overrides set higher up stop working in a subtree                                                              | `<ComponentProvider/>` used where you meant `<WithComponents/>`: it drops the keys of every provider above it                                                                                                    | Use `<WithComponents/>`                                                                                                                                                                                   |
| A `NotificationAnnouncer` override has no effect                                                               | It's set below `<Chat/>`, which reads it                                                                                                                                                                         | Set it in a `<WithComponents/>` around `<Chat/>`                                                                                                                                                          |
| A `MessageStatus` override that requires props, such as `({ label }: { label: string }) => …`, is a type error | The SDK renders the `MessageStatus` key without props                                                                                                                                                            | Render the exported component with its props inside the override: `MessageStatus: () => <MessageStatus MessageReadStatus={ReadIcon} />`                                                                   |
| Jumping to a message doesn't reach messages your `MessageListItem` renders                                     | The jump finds a message by the `data-message-id` in the `MessageListItemProps`, and your element doesn't carry them; see [Message list UI](https://getstream.io/chat/docs/sdk/react/ui/components/message-list-ui/#replacing-parts) | Spread the props onto the element: `({ children, ...props }) => <li {...props}>{children}</li>`                                                                                                           |
| An override, such as `LoadingIndicator`, shows in a place where you want the default                           | The SDK reads the key in most places it renders that part                                                                                                                                                        | Wrap that place in a `<WithComponents/>` with the exported default, in an object declared once at module scope: `const keepDefaultSpinner = { LoadingIndicator };`, then `overrides={keepDefaultSpinner}` |
| A custom `MessageUI` gets no message                                                                           | It expects props, and it receives none                                                                                                                                                                           | Read `useMessageContext()`                                                                                                                                                                                |
| A `MessageUI` change doesn't show in `<VirtualizedMessageList/>`                                               | `VirtualMessage` is set, and it wins there                                                                                                                                                                       | Change `VirtualMessage` too, or unset it                                                                                                                                                                  |
| An `AttachmentSelector` override also replaces the upload button of an inline editor                           | In a composer rendered inside a message, the SDK uses a simpler selector that only uploads files. A component set on the key replaces both.                                                                      | Inside a message, render the exported `<SimpleAttachmentSelector/>` from your component                                                                                                                   |

## Used in

- Most Features pages and every recipe, for example [Custom message](https://getstream.io/chat/docs/sdk/react/ui-cookbooks/message-list/custom-message/) and [Custom composer](https://getstream.io/chat/docs/sdk/react/ui-cookbooks/composer/custom-composer/).

---

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