import { Channel, MessageComposer, MessageList } from "stream-chat-react";
const sortByReactionCount = (a, b) => b.reactionCount - a.reactionCount;
const App = () => (
<Channel>
<MessageList sortReactions={sortByReactionCount} />
<MessageComposer />
</Channel>
);Reactions
The SDK includes built-in message reactions. The main reaction surfaces are:
ReactionSelectorfor selecting a reactionMessageReactionsfor displaying grouped reactions on a messageMessageReactionsDetailfor the user-detail dialog content
Best Practices
- Keep reaction options concise so the selector stays scannable.
- Memoize custom
sortReactionscomparators. - Build a custom compact reaction UI around
MessageReactionsonly when the default clustered or segmented styles are not enough. - Customize the selector and detail dialog together so their emoji sets stay aligned.
- If you replace the clustered reactions trigger, preserve its dialog-state ARIA attributes such as
aria-expandedandaria-pressed. - Use
reaction_groupsfor grouped reaction summaries.
Sorting Reactions
By default, reactions are sorted chronologically by the first time a reaction type was used. You can change this with sortReactions on MessageList or VirtualizedMessageList.
Pass reactionDetailsSort to MessageList, VirtualizedMessageList, Message, or MessageReactions to control the server-side ordering of users in the default reaction-detail dialog.
Customization
Use WithComponents to replace the default reaction surfaces for a Channel subtree:
import {
Channel,
ChannelHeader,
MessageComposer,
MessageList,
MessageReactions,
MessageReactionsDetail,
ReactionSelector,
Thread,
Window,
WithComponents,
} from "stream-chat-react";
const CustomReactionSelector = (props) => <ReactionSelector {...props} />;
const CustomMessageReactions = (props) => (
<MessageReactions {...props} visualStyle="segmented" />
);
const CustomMessageReactionsDetail = (props) => (
<MessageReactionsDetail {...props} />
);
const App = () => (
<WithComponents
overrides={{
MessageReactionsDetail: CustomMessageReactionsDetail,
MessageReactions: CustomMessageReactions,
ReactionSelector: CustomReactionSelector,
}}
>
<Channel>
<Window>
<ChannelHeader />
<MessageList />
<MessageComposer />
</Window>
<Thread />
</Channel>
</WithComponents>
);Detail Dialog Behavior
MessageReactionsDetail now treats selectedReactionType={null} as the "show all reactions" state.
- clicking the currently selected reaction type again clears the filter and returns to the all-reactions view
- when no reaction type is selected, each user row can show the emoji for the reaction that user sent
- the clustered
MessageReactionstrigger reflects dialog state witharia-expandedandaria-pressed
If you rebuild the detail view from scratch, preserve that null filter state instead of treating it as "nothing selected".
Loading State
The SDK exports MessageReactionsDetailLoadingIndicator for the default reactions-detail skeleton. Use it directly in custom detail UIs when you want the same loading appearance:
import { MessageReactionsDetailLoadingIndicator } from "stream-chat-react";
const CustomReactionsDetailLoadingState = () => (
<MessageReactionsDetailLoadingIndicator />
);Positioning
Use verticalPosition to control whether MessageReactions renders above or below the message bubble.
The SDK defaults to verticalPosition="top".
import { MessageReactions } from "stream-chat-react";
const CustomMessageReactions = (props) => (
<MessageReactions
{...props}
verticalPosition="bottom"
visualStyle="segmented"
/>
);verticalPosition works together with the existing layout props:
- use
verticalPosition="bottom"to move the reactions list below the message bubble - keep
verticalPosition="top"for the default layout - use
flipHorizontalPositionwhen you want to change the horizontal anchoring relative to the message alignment - set
verticalPosition={null}only when you want to remove the default top/bottom modifier class and fully control positioning in a custom wrapper
One implementation detail worth knowing: in segmented mode, the SDK only applies the built-in 4-reaction cap and overflow counter when verticalPosition="top". If you move segmented reactions to the bottom, the full processed list is rendered instead of the top-position capped variant.
ReactionSelector Props
| Prop | Description | Type |
|---|---|---|
handleReaction | Function that adds or removes a reaction. Overrides the value from MessageContext. | (reactionType: string, event: React.BaseSyntheticEvent) => Promise<void> |
own_reactions | Own reactions used to highlight the selected state. | ReactionResponse[] |
MessageReactions Props
| Prop | Description | Type |
|---|---|---|
flipHorizontalPosition | Controls whether the horizontal position is flipped relative to the current message alignment. Defaults to false. | boolean |
handleFetchReactions | Custom loader for reaction details. | MessageContextValue["handleFetchReactions"] |
own_reactions | Own reactions used to highlight the current user's reactions. | ReactionResponse[] |
reaction_groups | Grouped reaction summary used to build the list. | Record<string, ReactionGroupResponse> |
reactionDetailsSort | Sort options used when loading reaction details for MessageReactionsDetail. | MessageContextValue["reactionDetailsSort"] |
reactions | Raw reaction objects used to build the grouped list. | ReactionResponse[] |
reverse | Displays reactions in reverse order. Defaults to false. | boolean |
sortReactions | Comparator used to order grouped reactions. | ReactionsComparator |
verticalPosition | Controls whether the list renders above or below the message bubble. Defaults to 'top'. | 'top' | 'bottom' | null |
visualStyle | Controls whether the list renders in clustered or segmented mode. Defaults to 'clustered'. | 'clustered' | 'segmented' | null |
If you need a denser inline reactions presentation than the default clustered or segmented modes, build a custom component around the current reaction data and register it through WithComponents overrides={{ MessageReactions: CustomMessageReactions }}.
MessageReactionsDetail Props
| Prop | Description | Type |
|---|---|---|
handleFetchReactions | Custom loader for fetching reaction details. | MessageContextValue["handleFetchReactions"] |
onSelectedReactionTypeChange | Callback used when the selected reaction type changes. | (reactionType: ReactionType | null) => void |
reactionGroups | Grouped reaction summary used to keep the detail view in sync when reactions are removed. | Record<string, ReactionGroupResponse> |
reactionDetailsSort | Sort options used to fetch reaction details. | MessageContextValue["reactionDetailsSort"] |
reactions | Grouped reaction summary used to build the detail UI. | ReactionSummary[] |
selectedReactionType | Currently selected reaction type in the detail view. Use null for the "show all reactions" state. | ReactionType | null |
totalReactionCount | Total number of reactions shown in the detail dialog. | number |