import {
filterRenderTextMentionEntitiesByChannelCapabilities,
isMentionTypeAllowedByChannelCapabilities,
MENTION_TYPE_CAPABILITY,
MessageList,
renderText,
useChannelStateContext,
} from "stream-chat-react";
const CustomMessageList = () => {
// `channelCapabilities` is a `Record<string, boolean>` of the current user's
// allowed permissions on the active channel (sourced from `own_capabilities`).
const { channelCapabilities } = useChannelStateContext();
// Check a single mention type. `user` is always allowed; the others require the
// matching capability, which you can look up in `MENTION_TYPE_CAPABILITY`
// (for example, `MENTION_TYPE_CAPABILITY.user_group` is "notify-group").
const canMentionChannel = isMentionTypeAllowedByChannelCapabilities(
"channel",
channelCapabilities,
);
// Build mention metadata yourself and drop the types this channel isn't allowed
// to use, then forward the survivors to `renderText` as `messageMentionEntities`
// so only permitted mentions get highlighted in rendered message text.
const customRenderText = (text, mentionedUsers, options) =>
renderText(text, mentionedUsers, {
...options,
messageMentionEntities:
filterRenderTextMentionEntitiesByChannelCapabilities(
[
{ id: "channel", mentionType: "channel", name: "channel" },
{ id: "admin", mentionType: "role", name: "admin" },
],
channelCapabilities,
),
});
return canMentionChannel ? (
<MessageList renderText={customRenderText} />
) : null;
};Autocomplete Suggestions
The message composer supports autocompletion for mentions, commands, and emojis.
Autocomplete suggestions are triggered by typing:
| Trigger | Action | Example |
|---|---|---|
@ | mention | @tom, @channel, @here, @admin, @Support Team |
/ | command | /giphy |
: | emoji | :smiling |
Enabling Mention Types
The @ trigger can surface five mention types. User mentions always work, but the broadcast, role, and user-group variants are gated by channel capabilities, so they only appear once the matching permission is granted to the sender's role.
| Mention type | Token | Channel capability | Dashboard permission |
|---|---|---|---|
user | @<name> | always available | — |
channel | @channel | notify-channel | NotifyChannel |
here | @here | notify-here | NotifyHere |
role | @<role> | notify-role | NotifyRole |
user_group | @<group> | notify-group | NotifyGroup |
Grant these permissions in the Stream Dashboard under Chat Messaging → Roles & Permissions: pick the role (for example channel_member) and the channel-type scope (for example messaging), then enable the relevant permissions. They surface on the channel as channel.data.own_capabilities, and the composer checks them at query time, so suggestions update if capabilities change. Until a capability is present, that mention type is filtered out of the suggestion list.
Two additional requirements:
- Role mentions resolve against the roles defined under the same Roles & Permissions dashboard page.
- User-group mentions require user groups to exist in your app. Define and manage them as described in the Mentions guide.
If you need to run the same checks yourself, stream-chat-react exports the capability helpers — isMentionTypeAllowedByChannelCapabilities, the MENTION_TYPE_CAPABILITY map, and filterRenderTextMentionEntitiesByChannelCapabilities (used to drop disallowed mentions before rendering message text):
The filtered entities are passed to renderText through options.messageMentionEntities — the same additive mention-metadata path described in the renderText guide. Anything dropped by the filter is never highlighted in the rendered message.
Best Practices
- Keep suggestion lists keyboard accessible and preserve the default focus behavior.
- Use
SuggestionListanddefaultComponentsas the starting point for custom list UI. - Override the list through
WithComponentsso every nested composer stays consistent. - Keep custom suggestion item rendering lightweight.
- Prefer using
TextareaComposerinside custom inputs instead of rebuilding autocomplete behavior from scratch.
Customizing Suggestion Items
The default list implementation is SuggestionList. The simplest way to customize item rendering is to wrap it and replace one or more suggestionItemComponents.
The @ suggestion type can include users, @channel, @here, roles, and user groups. If you only want to customize user rows, keep the default MentionItem and replace its nested UserItem instead of replacing the whole @ renderer.
import { SearchIndex } from "emoji-mart";
import type { MentionSuggestion } from "stream-chat";
import {
Avatar,
Channel,
MentionItem,
MessageComposer,
SuggestionList,
type SuggestionListItemComponentProps,
type SuggestionListProps,
type UserItemProps,
defaultComponents,
WithComponents,
} from "stream-chat-react";
const CustomUserItem = ({ entity, focused, ...buttonProps }: UserItemProps) => {
const title = entity.name ?? entity.id;
if (!title) return null;
return (
<button
{...buttonProps}
className={`suggestion-list__item ${focused ? "suggestion-list__item--selected" : ""}`}
>
<Avatar imageUrl={entity.image} size={32} userName={title} />
<span>{title}</span>
</button>
);
};
const CustomMentionItem = (props: SuggestionListItemComponentProps) => (
<MentionItem
{...props}
entity={props.entity as MentionSuggestion}
UserItem={CustomUserItem}
/>
);
const CustomSuggestionList = (props: SuggestionListProps) => (
<SuggestionList
{...props}
suggestionItemComponents={{
...defaultComponents,
"@": CustomMentionItem,
}}
/>
);
const App = () => (
<WithComponents
overrides={{
AutocompleteSuggestionList: CustomSuggestionList,
emojiSearchIndex: SearchIndex,
}}
>
<Channel>
<MessageComposer />
</Channel>
</WithComponents>
);.suggestion-list__item {
display: flex;
align-items: center;
gap: 10px;
width: 100%;
padding: 12px;
border: 0;
background: none;
font: inherit;
}
.suggestion-list__item--selected {
background: #00000014;
}Customizing Rows Per Mention Type
MentionItem is a dispatcher: it reads entity.mentionType and renders a dedicated row component for each variant. You can override any subset of these and let the defaults handle the rest — each override receives the same props (entity, focused, and the button props), with entity narrowed to the matching MentionSuggestion variant.
mentionType | Override prop | Default renders |
|---|---|---|
user | UserItem | Avatar + display name |
channel / here | BroadcastMentionItem | @channel / @here with a description |
role | RoleItem | Role name |
user_group | UserGroupItem | Group name + optional description |
| other | SpecialMentionItem | Fallback row |
For example, to customize only the user-group row while keeping every other variant default:
import type { UserGroupMentionSuggestion } from "stream-chat";
import {
MentionItem,
type SuggestionListItemComponentProps,
type UserGroupItemProps,
} from "stream-chat-react";
const CustomUserGroupItem = ({
entity,
focused,
...buttonProps
}: UserGroupItemProps) => (
<button
{...buttonProps}
className={`suggestion-list__item ${focused ? "suggestion-list__item--selected" : ""}`}
>
<span>👥 {entity.name}</span>
</button>
);
const CustomMentionItem = (props: SuggestionListItemComponentProps) => (
<MentionItem
{...props}
entity={props.entity as UserGroupMentionSuggestion}
UserGroupItem={CustomUserGroupItem}
/>
);Register CustomMentionItem as the "@" entry of suggestionItemComponents exactly as in the user-row example above.
Wrapping The Default Suggestion List
If you only need extra framing around the list, keep the default list behavior and wrap SuggestionList:
import { SuggestionList, type SuggestionListProps } from "stream-chat-react";
const SuggestionListWithHeader = (props: SuggestionListProps) => (
<div className="suggestion-list-shell">
<div className="suggestion-list-shell__header">Suggestions</div>
<SuggestionList {...props} />
</div>
);Register it with WithComponents the same way:
<WithComponents
overrides={{ AutocompleteSuggestionList: SuggestionListWithHeader }}
>
<Channel>
<MessageComposer />
</Channel>
</WithComponents>Replacing AutocompleteSuggestionItem
If you need to replace the selection wrapper itself, override AutocompleteSuggestionItem.
The current item wrapper receives SuggestionItemProps, which include:
itemfocusedcomponent- the button props passed through to the rendered item
import {
SuggestionListItem,
type SuggestionItemProps,
} from "stream-chat-react";
const CustomAutocompleteSuggestionItem = (props: SuggestionItemProps) => (
<SuggestionListItem
{...props}
className="custom-autocomplete-suggestion-item"
/>
);For most apps, overriding AutocompleteSuggestionList and using suggestionItemComponents is enough. Reach for AutocompleteSuggestionItem only when you want to change the shared keyboard and selection wrapper.
Custom Inputs
If you build a custom input layout, keep TextareaComposer in the tree and override the suggestion list through WithComponents. That preserves the SDK text composer, keyboard navigation, and suggestion positioning logic.
For a full custom input example, see the Input UI cookbook.