# Custom suggestions

> **Info:** This is release candidate documentation for **Stream Chat React SDK v15**. For the latest stable version, see the [latest version (v14)](https://getstream.io/chat/docs/sdk/react/).

The composer suggests items while the user types a trigger:

| Trigger | Suggests               | Example                                                |
| ------- | ---------------------- | ------------------------------------------------------ |
| `@`     | people and groups      | `@tom`, `@channel`, `@here`, `@admin`, `@Support Team` |
| `/`     | commands               | `/giphy`                                               |
| `:`     | emoji, once you add it | `:smiling`                                             |

This recipe changes how the suggestion list and its rows look, and adds the rows for a trigger of your own. Where the suggestions come from, and how to add a trigger, is explained in [Suggestions](https://getstream.io/chat/docs/sdk/react/v15/state/writing-messages/suggestions/). Emoji suggestions need the emoji middleware: see [Emoji](https://getstream.io/chat/docs/sdk/react/v15/features/rich-content/emoji/#autocomplete).

## Enabling mention types

User mentions always work. `@channel`, `@here`, roles and user groups appear only for users with the matching capability, and roles and user groups also need data on the server. [Mentions](https://getstream.io/chat/docs/sdk/react/v15/features/messaging/mentions/#turning-it-on-or-off) lists the capabilities and where to grant them. The composer checks them itself, so a row you replace never shows a mention the user can't make.

## Best practices

- Start from `SuggestionList` and `defaultComponents`, and replace only the rows you need.
- Register the list through `WithComponents` with the `AutocompleteSuggestionList` key, so every composer below it uses it, in channels and threads alike.
- Spread the props a row receives onto its button. They carry the click handler, the `option` role and the selection state that keyboard navigation relies on.
- Keep rows light: the list re-renders them as the user types.
- In a composer of your own, keep `TextareaComposer`. It opens the list, positions it and handles the arrow keys and Enter.

## Customizing suggestion items

`SuggestionList` renders the open list. Its `suggestionItemComponents` prop maps each trigger to the component of a row, and `defaultComponents` holds the defaults for `@`, `/` and `:`.

The `@` row is `MentionItem`. It renders a different row for each kind of mention, so to change only the people, keep `MentionItem` and replace its `UserItem`:

```tsx title="src/CustomSuggestionList.tsx"
import type { MentionSuggestion } from "stream-chat";
import type {
  SuggestionListItemComponentProps,
  SuggestionListProps,
  UserItemProps,
} from "stream-chat-react";
import {
  Avatar,
  defaultComponents,
  MentionItem,
  SuggestionList,
} 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-row ${focused ? "suggestion-row--focused" : ""}`}
      type="button"
    >
      <Avatar imageUrl={entity.image} size="sm" userName={title} />
      <span>{title}</span>
    </button>
  );
};

const CustomMentionItem = (props: SuggestionListItemComponentProps) => (
  <MentionItem
    {...props}
    entity={props.entity as MentionSuggestion}
    UserItem={CustomUserItem}
  />
);

const rows = { ...defaultComponents, "@": CustomMentionItem };

export const CustomSuggestionList = (props: SuggestionListProps) => (
  <SuggestionList {...props} suggestionItemComponents={rows} />
);
```

```css title="src/CustomSuggestionList.css"
.suggestion-row {
  align-items: center;
  background: none;
  border: 0;
  display: flex;
  font: inherit;
  gap: 10px;
  padding: 8px 12px;
  width: 100%;
}

.suggestion-row--focused {
  background: #00000014;
}
```

Register the list once, around the app:

```tsx title="src/App.tsx"
import type { PropsWithChildren } from "react";
import { WithComponents } from "stream-chat-react";
import { CustomSuggestionList } from "./CustomSuggestionList";
import "./CustomSuggestionList.css";

const overrides = { AutocompleteSuggestionList: CustomSuggestionList };

export const AppComponents = ({ children }: PropsWithChildren) => (
  <WithComponents overrides={overrides}>{children}</WithComponents>
);
```

### Customizing rows per mention type

`MentionItem` reads `entity.mentionType` and renders the matching row. Replace any of them and the rest keep their defaults. Each row receives the same props: `entity`, narrowed to its kind of mention, `focused`, and the button props.

| `mentionType`      | Prop of `MentionItem`  | Default row                                |
| ------------------ | ---------------------- | ------------------------------------------ |
| `user`             | `UserItem`             | Avatar and name                            |
| `channel` / `here` | `BroadcastMentionItem` | `@channel` or `@here` with a description   |
| `role`             | `RoleItem`             | The role's name and "Notify all … members" |
| `user_group`       | `UserGroupItem`        | The group's name and description           |
| any other          | `SpecialMentionItem`   | Nothing                                    |

This one changes only the user group row:

```tsx title="src/GroupMentionItem.tsx"
import type { MentionSuggestion } from "stream-chat";
import type {
  SuggestionListItemComponentProps,
  UserGroupItemProps,
} from "stream-chat-react";
import { MentionItem } from "stream-chat-react";

const CustomUserGroupItem = ({
  entity,
  focused,
  ...buttonProps
}: UserGroupItemProps) => (
  <button
    {...buttonProps}
    className={`suggestion-row ${focused ? "suggestion-row--focused" : ""}`}
    type="button"
  >
    👥 {entity.name}
  </button>
);

export const GroupMentionItem = (props: SuggestionListItemComponentProps) => (
  <MentionItem
    {...props}
    entity={props.entity as MentionSuggestion}
    UserGroupItem={CustomUserGroupItem}
  />
);
```

Use `GroupMentionItem` as the `"@"` entry of `suggestionItemComponents`, as in the example above.

### Rows for a trigger of your own

A trigger of your own needs a row in `suggestionItemComponents`, under the trigger character. Without one, the list doesn't open. [Custom triggers](https://getstream.io/chat/docs/sdk/react/v15/state/writing-messages/suggestions/#custom-triggers) adds a `#` trigger that suggests tags, each `{ id, name }`. This list renders its rows:

```tsx title="src/SuggestionListWithTags.tsx"
import type {
  SuggestionListItemComponentProps,
  SuggestionListProps,
} from "stream-chat-react";
import { defaultComponents, SuggestionList } from "stream-chat-react";

type Tag = { id: string; name: string };

const TagItem = ({
  entity,
  focused,
  ...buttonProps
}: SuggestionListItemComponentProps) => (
  <button
    {...buttonProps}
    className={`suggestion-row ${focused ? "suggestion-row--focused" : ""}`}
    type="button"
  >
    #{(entity as Tag).name}
  </button>
);

const rows = { ...defaultComponents, "#": TagItem };

export const SuggestionListWithTags = (props: SuggestionListProps) => (
  <SuggestionList {...props} suggestionItemComponents={rows} />
);
```

Register `SuggestionListWithTags` under the `AutocompleteSuggestionList` key. Picking a tag runs the middleware's `onSuggestionItemSelect`, which writes it into the text.

## Wrapping the default suggestion list

To add something around the list and keep its behaviour, wrap `SuggestionList`:

```tsx title="src/SuggestionListWithHeader.tsx"
import type { SuggestionListProps } from "stream-chat-react";
import { SuggestionList } from "stream-chat-react";

export const SuggestionListWithHeader = (props: SuggestionListProps) => (
  <div className="suggestion-list-shell">
    <div className="suggestion-list-shell__header">Suggestions</div>
    <SuggestionList {...props} />
  </div>
);
```

Register it with the `AutocompleteSuggestionList` key, like the lists above.

## Replacing `AutocompleteSuggestionItem`

Each row sits in a wrapper that handles selection: it scrolls the focused row into view and, on click, writes the item into the text and moves the focus back to the input. To change the wrapper for every trigger at once, override `AutocompleteSuggestionItem`. It receives `SuggestionItemProps`:

- `item`, the suggestion;
- `focused`, whether the row is the one the arrow keys reached;
- `component`, the row component for the open trigger;
- the button props it passes on to the row.

This one adds a class to every row and keeps the default wrapper:

```tsx title="src/CustomAutocompleteSuggestionItem.tsx"
import type { SuggestionItemProps } from "stream-chat-react";
import { SuggestionListItem } from "stream-chat-react";

export const CustomAutocompleteSuggestionItem = (
  props: SuggestionItemProps,
) => <SuggestionListItem {...props} className="custom-suggestion-item" />;
```

Most apps need only `AutocompleteSuggestionList` and `suggestionItemComponents`. Override `AutocompleteSuggestionItem` when you change how a row is selected.

## Custom inputs

A composer layout of your own keeps the suggestions as long as it renders `TextareaComposer`: the list, the keyboard navigation and its position next to the cursor come with it. The overrides on this page apply inside it too. The [Custom composer](https://getstream.io/chat/docs/sdk/react/v15/recipes/composer/custom-composer/) recipe builds such a layout.

---

For the most recent version of this documentation, visit [https://getstream.io/chat/docs/sdk/react/v15/recipes/composer/custom-suggestions/](https://getstream.io/chat/docs/sdk/react/v15/recipes/composer/custom-suggestions/).