import { Channel, WithComponents } from "stream-chat-react";
const customReactionOptions = {
quick: {
arrow_down: {
Component: () => <>⬇️</>,
name: "Down vote",
},
arrow_up: {
Component: () => <>⬆️</>,
name: "Up vote",
},
},
extended: {
arrow_down: {
Component: () => <>⬇️</>,
name: "Down vote",
},
arrow_up: {
Component: () => <>⬆️</>,
name: "Up vote",
},
fire: {
Component: () => <>🔥</>,
name: "Fire",
},
},
};
export const WrappedChannel = ({ children }) => (
<WithComponents overrides={{ reactionOptions: customReactionOptions }}>
<Channel>{children}</Channel>
</WithComponents>
);Reactions Customization
This example shows how to customize the SDK reaction surfaces for a channel subtree.
Best Practices
- Keep custom reactions aligned with your product’s interaction model.
- Register shared reaction options through
WithComponentsso the selector and list stay in sync. - Prefer the
{ quick, extended }shape when you need different sets for the compact selector and expanded picker. - Keep custom reaction handlers permission-aware and idempotent.
- Test custom reactions in both channel and thread message lists.
Shared Reaction Options
reactionOptions lives in ComponentContext, so the usual override path is WithComponents.

ReactionsList still supports the older array form when you pass reactionOptions directly to that component, but ReactionSelector now uses the shared context value and the { quick, extended } shape is the best fit for selector customization.
Override The Default Reaction Surfaces
Use WithComponents to replace ReactionSelector, ReactionsList, or MessageReactionsDetail.
import {
Channel,
MessageReactionsDetail,
ReactionSelector,
ReactionsList,
WithComponents,
} from "stream-chat-react";
const CustomReactionSelector = (props) => <ReactionSelector {...props} />;
const CustomReactionsList = (props) => (
<ReactionsList {...props} visualStyle="segmented" />
);
const CustomMessageReactionsDetail = (props) => (
<MessageReactionsDetail {...props} />
);
export const WrappedChannel = ({ children }) => (
<WithComponents
overrides={{
MessageReactionsDetail: CustomMessageReactionsDetail,
ReactionSelector: CustomReactionSelector,
ReactionsList: CustomReactionsList,
}}
>
<Channel>{children}</Channel>
</WithComponents>
);Here's the difference between the two visualStyle options for ReactionsList:
| Clustered | Segmented |
|---|---|
![]() | ![]() |
Custom Reaction Handler
If you want to change behavior without replacing the whole selector UI, wrap ReactionSelector and override handleReaction.
import { useCallback } from "react";
import {
Channel,
ReactionSelector,
WithComponents,
useChannelStateContext,
useMessageContext,
} from "stream-chat-react";
const CustomReactionSelector = (props) => {
const {
message: { id: messageId, own_reactions: ownReactions = [] },
} = useMessageContext("CustomReactionSelector");
const { channel } = useChannelStateContext("CustomReactionSelector");
const handleReaction = useCallback(
async (reactionType, event) => {
console.log({ event });
const hasReactedWithType = ownReactions.some(
(reaction) => reaction.type === reactionType,
);
if (hasReactedWithType) {
await channel.deleteReaction(messageId, reactionType);
return;
}
await channel.sendReaction(messageId, { type: reactionType });
},
[channel, messageId, ownReactions],
);
return <ReactionSelector {...props} handleReaction={handleReaction} />;
};
export const WrappedChannel = ({ children }) => (
<WithComponents overrides={{ ReactionSelector: CustomReactionSelector }}>
<Channel>{children}</Channel>
</WithComponents>
);Read More
The reference page for the current reaction surfaces lives here:

