# 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](https://getstream.io/signin/?product=chat) 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](https://getstream.io/chat/docs/sdk/react/guides/mentions/#user-groups).

There is no built-in capability-filter helper. If you need to run the same checks yourself, build the mention metadata for the current channel and drop the types whose capability is missing from `channelCapabilities` before handing the survivors to `renderText` through `options.messageMentionEntities`. The capability tokens are the ones listed in the table above.

```tsx
import {
  MessageList,
  renderText,
  useChannelStateContext,
  type RenderTextMentionEntity,
} from "stream-chat-react";

// Map each gated mention type to the capability that must be present on the
// channel. `user` mentions are always allowed and need no capability.
const mentionTypeToCapability = {
  channel: "notify-channel",
  here: "notify-here",
  role: "notify-role",
  user_group: "notify-group",
} as const;

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();

  const isMentionTypeAllowed = (
    mentionType: RenderTextMentionEntity["mentionType"],
  ) => {
    if (mentionType === "user") return true;
    const capability = mentionTypeToCapability[mentionType];
    return Boolean(capability && channelCapabilities[capability]);
  };

  // Build the mention metadata yourself, 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: (
        [
          { id: "channel", mentionType: "channel", name: "channel" },
          { id: "admin", mentionType: "role", name: "admin" },
        ] satisfies RenderTextMentionEntity[]
      ).filter((entity) => isMentionTypeAllowed(entity.mentionType)),
    });

  return <MessageList renderText={customRenderText} />;
};
```

The filtered entities are passed to `renderText` through `options.messageMentionEntities` — the same additive mention-metadata path described in the [renderText guide](https://getstream.io/chat/docs/sdk/react/components/message-components/render-text/#existing-remark-and-rehype-plugins). 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 `SuggestionList` and `defaultComponents` as the starting point for custom list UI.
- Override the list through `WithComponents` so every nested composer stays consistent.
- Keep custom suggestion item rendering lightweight.
- Prefer using `TextareaComposer` inside 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.

```tsx
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="md" 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>
);
```

```css
.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:

```tsx
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`:

```tsx
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:

```tsx
<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:

- `item`
- `focused`
- `component`
- the button props passed through to the rendered item

```tsx
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](https://getstream.io/chat/docs/sdk/react/guides/theming/input-ui/).


---

This page was last updated at 2026-08-13T00:26:24.496Z.

For the most recent version of this documentation, visit [https://getstream.io/chat/docs/sdk/react/guides/customization/suggestion-list/](https://getstream.io/chat/docs/sdk/react/guides/customization/suggestion-list/).