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

  • Start with theming; most icon color and size tweaks can be done there without a custom icon.
  • Override icons with WithComponents under the icons key — you only need to provide the icons you want to change.
  • Have custom icons accept IconProps and honor the height/width/size and color (fill/stroke/pathFill) values the SDK passes, so they match sizing and theming everywhere they render.
  • 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 Svg, { Path } from "react-native-svg";
import {
  WithComponents,
  Channel,
  MessageList,
  MessageInput,
  IconProps,
} from "stream-chat-react-native";

// A custom mute icon. It receives IconProps, so honor the size and color
// the SDK passes to keep it consistent with the surrounding UI.
const CustomMuteIcon = ({
  height = 24,
  width = 24,
  fill = "#000",
  ...rest
}: IconProps) => (
  <Svg height={height} width={width} viewBox="0 0 24 24" {...rest}>
    <Path
      d="M3 3l18 18M9 9v6a3 3 0 004.5 2.6M12 4a3 3 0 013 3v3"
      fill="none"
      stroke={fill}
      strokeWidth={2}
    />
  </Svg>
);

<WithComponents overrides={{ icons: { Mute: CustomMuteIcon } }}>
  <Channel channel={channel}>
    <MessageList />
    <MessageInput />
  </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 thread-reply icons:

<WithComponents
  overrides={{
    icons: {
      SendRight: CustomSendIcon,
      ThreadReply: CustomThreadReplyIcon,
    },
  }}
>
  <Channel channel={channel}>
    <MessageList />
    <MessageInput />
  </Channel>
</WithComponents>

Icon overrides are merged per-icon: providing icons={{ Mute: 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 IconProps. It doesn't have to be an SVG — you can render an Image, a component from another icon library, or anything else. Honor height/width so it lines up with the layout the SDK expects.

import { Image } from "react-native";
import {
  WithComponents,
  Channel,
  MessageList,
  MessageInput,
  IconProps,
} from "stream-chat-react-native";

const ImageSendIcon = ({ height = 24, width = 24 }: IconProps) => (
  <Image source={require("./assets/send.png")} style={{ height, width }} />
);

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

How icon props work

Every SDK icon accepts IconProps:

type IconProps = Partial<SvgProps> & {
  height?: number;
  width?: number;
  size?: number;
  pathFill?: SvgProps["fill"];
  pathOpacity?: PathProps["opacity"];
};

When the SDK renders an icon it passes the appropriate size (height/width, or size) and color for the context — usually via fill, stroke, or pathFill, sourced from the active theme. A well-behaved custom icon reads those values instead of hard-coding them, so it scales and recolors the same way the default did in each place it appears.

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: { SendRight: BrandSendIcon } }}>
  <Chat client={client}>
    {/* This channel uses a different send icon; everything else inherits BrandSendIcon */}
    <WithComponents overrides={{ icons: { SendRight: SpecialSendIcon } }}>
      <Channel channel={channel}>
        <MessageList />
        <MessageInput />
      </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 useComponentsContext(). This lets a custom component reuse the same (possibly overridden) icons the SDK uses.

import { useComponentsContext } from "stream-chat-react-native";

const MyCustomHeader = () => {
  const { icons } = useComponentsContext();

  return (
    <View>
      <icons.Search height={20} width={20} />
    </View>
  );
};