Custom Icons

Every icon the SDK renders — the send arrow, delivery checkmarks, the mute and pin badges, the attachment-picker glyphs, and more — can be replaced with your own. Icons are exposed through the same WithComponents provider used for other UI components, grouped under a single icons key.

Best Practices

  • Override icons with WithComponents under the icons key — you only need to provide the icons you want to change.
  • Have custom icons match IconComponent interface and honor the expected defaults used in default SDK icons (viewBox se to 0 0 20 20 and fill/stroke to either none or currentColor) if your custom component returns an SVG.
  • Preserve className property coming from the SDK and reuse str-chat__icon class for easier styling adjustments.
  • Set app-wide icon overrides high in the tree (around Chat), and override more specifically deeper down only when needed.
  • Keep custom icons lightweight — the same icon may render once per message row inside MessageList.

Overriding an icon

Pass an icons object to the overrides prop of WithComponents. Each key is the name of an SDK icon, and the value is your replacement component. Only the icons you list are replaced — every other icon keeps its default.

import {
  WithComponents,
  Channel,
  MessageList,
  MessageComposer,
  ChannelList,
  type IconComponent,
} from "stream-chat-react";
import { clsx } from "clsx";

// SDK may pass props to your icon component, adjust your defaults and spread the rest (SVG only)
const CustomMuteIcon: IconComponent = ({ className, ...rest }) => (
  <svg
    viewBox="0 0 20 20"
    className={clsx("str-chat__icon", className)}
    {...rest}
  >
    <path
      d="M3 3l18 18M9 9v6a3 3 0 004.5 2.6M12 4a3 3 0 013 3v3"
      fill="none"
      stroke="currentColor"
      strokeWidth="1.5"
    />
  </svg>
);

<WithComponents overrides={{ icons: { IconMute: CustomMuteIcon } }}>
  <ChannelList />
  <Channel>
    <MessageList />
    <MessageComposer />
  </Channel>
</WithComponents>;

Your icon is now used everywhere the SDK renders the mute icon within that subtree — for example, in the channel list preview and channel details.

Overriding multiple icons

Add as many entries as you need. Sibling defaults are preserved, so this replaces only the send and reply icons:

<WithComponents
  overrides={{
    icons: {
      IconSend: CustomSendIcon,
      IconReply: CustomReplyIcon,
    },
  }}
>
  <Channel channel={channel}>
    <MessageList />
    <MessageComposer />
  </Channel>
</WithComponents>

Icon overrides are merged per-icon: providing icons={{ IconMute: CustomMuteIcon }} swaps the mute icon while leaving all other icons at their defaults. You never have to supply the full icon set.

Using any component as an icon

A custom icon is just a React component that accepts SVG props. It doesn't have to be an SVG — you can render an image, a component from another icon library, or anything else.

import {
  WithComponents,
  Channel,
  MessageList,
  MessageComposer,
  type IconComponent,
} from "stream-chat-react";
import { clsx } from "clsx";

const ImageSendIcon: IconComponent = ({ className }) => (
  <image
    src="path/to/image.jpg"
    className={clsx("str-chat__icon", className)}
  />
);

<WithComponents overrides={{ icons: { SendRight: ImageSendIcon } }}>
  <Channel channel={channel}>
    <MessageList />
    <MessageComposer />
  </Channel>
</WithComponents>;

How icon props work

Every SDK icon accepts props based on an IconComponent interface that looks like this:

import type { ComponentPropsWithoutRef, ComponentType } from "react";

export type IconComponent = ComponentType<ComponentPropsWithoutRef<"svg">>;

When the SDK renders an icon it may pass some additional properties (like a specific className or SVG overrides) - these properties may shape the icon. Well-behaved custom icon should rely on the CSS properties of the parent elements (such as currentColor) or theming-related CSS variables.

Inheritance

Icon overrides follow the same inheritance rules as other WithComponents overrides. Inner providers merge over outer ones, so you can set app-wide icons at the top of the tree and override a specific icon deeper down:

// App-wide default icons
<WithComponents overrides={{ icons: { IconSend: BrandSendIcon } }}>
  <Chat client={client}>
    {/* This channel uses a different send icon; everything else inherits BrandSendIcon */}
    <WithComponents overrides={{ icons: { IconSend: SpecialSendIcon } }}>
      <Channel channel={channel}>
        <MessageList />
        <MessageComposer />
      </Channel>
    </WithComponents>
  </Chat>
</WithComponents>

Reading the resolved icons

Inside your own custom components you can read the resolved icon set — defaults merged with any overrides — from useComponentsContextIcons(). This lets a custom component reuse the same (possibly overridden) icons the SDK uses.

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

const CustomSendButton = () => {
  const { IconSend } = useComponentsContextIcons();

  return (
    <div>
      <IconSend />
    </div>
  );
};