# Autocomplete Suggestions

Message input supports autocompletion for mentions, commands, and emojis.

Autocomplete suggestions are triggered by typing the special characters:

| Trigger | Action  | Example  |
| ------- | ------- | -------- |
| `@`     | mention | @user    |
| `/`     | command | /giphy   |
| `:`     | emoji   | :smiling |

## Best Practices

- Keep suggestion lists keyboard accessible with proper focus handling.
- Use type guards to render distinct UI per suggestion type.
- Avoid heavy rendering; suggestion lists should feel instant.
- Localize command descriptions with `TranslationContext`.
- Preserve the default list behavior unless you need structural changes.

The default message input component provided by the SDK supports this out of the box. When a trigger
character is typed into the message input, a list of suggested options appears:

![](@chat-sdk/react/v13/_assets/message-input-ui-suggestions.png)

To customize the suggestions list, you have two options:

1. Use the default message input provided by the SDK, and override the following components to
   customize the look and feel of the suggestion list:
   - [`AutocompleteSuggestionItem`](/chat/docs/sdk/react/v13/components/contexts/component_context#autocompletesuggestionitem/)
   - [`AutocompleteSuggestionList`](/chat/docs/sdk/react/v13/components/contexts/component_context#autocompletesuggestionlist/)

2. Implement the message input component from scratch, and add autocomplete functionality to it
   yourself.

Let's explore both options.

## Overriding the Suggestion Item Component

Let's start by creating a custom suggestion item component.

As usual, to override a component used by the SDK, you should pass a custom component as a prop to
the [`Channel`](/chat/docs/sdk/react/v13/components/core-components/channel/) component in your application code. In
this case we are overriding
[`AutocompleteSuggestionItem`](/chat/docs/sdk/react/v13/components/contexts/component_context#autocompletesuggestionitem/):

```tsx
import {
  Chat,
  Channel,
  ChannelHeader,
  ChannelList,
  MessageList,
  Thread,
  Window,
  MessageInput,
} from "stream-chat-react";
import { SearchIndex } from "emoji-mart";

export const App = () => (
  <Chat client={chatClient}>
    <ChannelList filters={filters} sort={sort} options={options} />
    <Channel
      AutocompleteSuggestionItem={CustomSuggestionItem}
      emojiSearchIndex={SearchIndex}
    >
      <Window>
        <ChannelHeader />
        <MessageList />
        <MessageInput />
      </Window>
      <Thread />
    </Channel>
  </Chat>
);
```

Since we’re only overriding the item, the default `AutocompleteSuggestionList` still provides data and callbacks via props.

The implementation is straightforward, but note:

1. To show different previews for different item types (e.g. we want to show avatars for users and
   emoji previews for emojis) we need to put in type guards for each item type.
2. The default `AutocompleteSuggestionList` requires you to call the `onSelectHandler` callback when
   an item is focused or hovered. This is to ensure that items in the list are keyboard accessible.

<Tabs>

</Tabs>

![](@chat-sdk/react/v13/_assets/message-input-ui-emoji-suggestions.png)

![](@chat-sdk/react/v13/_assets/message-input-ui-user-suggestions.png)

![](@chat-sdk/react/v13/_assets/message-input-ui-command-suggestions.png)

If you’re building an internationalized app, translate command descriptions/arguments using
the translation helper from
[`TranslationContext`](/chat/docs/sdk/react/v13/guides/theming/translations/). All you need to do is to query the translation
with the right key: `<command-name>-command-description` for the description, and
`<command-name>-command-args` for the arguments (you can always refer to our
[translation files](https://github.com/GetStream/stream-chat-react/blob/master/src/i18n/es.json) to
check if the key is correct).

```tsx
// In CustomSuggestionItem component:
const { t } = useTranslationContext();

// Item is a command configured for the current channel
if ("name" in item && "description" in item) {
  children = (
    <>
      <strong>/{item.name}</strong>
      {t(`${item.name}-command-description`, {
        defaultValue: item.description,
      })}
    </>
  );
}
```

![](@chat-sdk/react/v13/_assets/message-input-ui-suggestions-localized.png)

English (US) is loaded from the Stream backend as part of channel config, so use it as a fallback value.

## Overriding the Suggestion List Component

If you want to further customize the default behavior of the suggestion list, you can override the
entire list component.

This is an easy way to add a header or footer to the list. You don't have to reimplement the whole
list component, just create a small wrapper:

<Tabs>

</Tabs>

This wrapper uses two props from the default message input: `currentTrigger` (the trigger character) and `value` (the current query).

Then override the
[`AutocompleteSuggestionList`](/chat/docs/sdk/react/v13/components/contexts/component_context#autocompletesuggestionlist/)
list at the [`Channel`](/chat/docs/sdk/react/v13/components/core-components/channel/) level, and you're done:

```tsx
import {
  Chat,
  Channel,
  ChannelHeader,
  ChannelList,
  MessageList,
  Thread,
  Window,
  MessageInput,
} from "stream-chat-react";
import { SearchIndex } from "emoji-mart";

export const App = () => (
  <Chat client={chatClient}>
    <ChannelList filters={filters} sort={sort} options={options} />
    <Channel
      AutocompleteSuggestionList={SuggestionListWithHeader}
      emojiSearchIndex={SearchIndex}
    >
      <Window>
        <ChannelHeader />
        <MessageList />
        <MessageInput />
      </Window>
      <Thread />
    </Channel>
  </Chat>
);
```

![](@chat-sdk/react/v13/_assets/message-input-ui-suggestions-header.png)

You can also reimplement the list from scratch. In that case, you’re responsible for interactions and accessibility. The example below is a starting point, but it doesn’t handle keyboard interaction:

<Tabs>

</Tabs>

## Implementing Autocompletion for Custom Message Input

Finally, if you are implementing the entire message input component
[from scratch](/chat/docs/sdk/react/v13/guides/theming/input_ui/), it's up to you to build the autocomplete functionality. The
good news is that the
[`autocompleteTriggers`](/chat/docs/sdk/react/v13/components/contexts/message_input_context#autocompletetriggers/)
value in the `MessageInputContext` provides a lot of reusable functionality. The bad news is that
properly implementing autocomplete is still a lot of work.

Let's try to tackle that. We'll start with the simple message input implementation from the
[Message Input UI cookbook](/chat/docs/sdk/react/v13/guides/theming/input_ui/):

```tsx
import { useMessageInputContext } from "stream-chat-react";

const CustomMessageInput = () => {
  const { text, handleChange, handleSubmit } = useMessageInputContext();

  return (
    <div className="message-input">
      <textarea
        value={text}
        className="message-input__input"
        onChange={handleChange}
      />
      <button
        type="button"
        className="message-input__button"
        onClick={handleSubmit}
      >
        ⬆️
      </button>
    </div>
  );
};
```

The
[`autocompleteTriggers`](/chat/docs/sdk/react/v13/components/contexts/message_input_context#autocompletetriggers/)
object is a map between special characters that trigger autocompletion suggestions and some useful
functions:

- `dataProvider` returns (via a callback) the list of suggestions, given the current query (the text
  after the trigger)
- `callback` is the action that should be called when a suggestion is selected (e.g. when a user
  mention is selected, it modifies the message payload to include the mention)
- `output` function returns the replacement text, given one of the items returned by the
  `dataProvider`

The keys of the `autocompleteTriggers` are the characters that should trigger autocomplete
suggestions.

We'll start by using the `dataProvider` to display the list of suggestions once the trigger
character is entered by the user. We use a (memoized) regular expression to find trigger characters,
and then we query the `dataProvider` for suggestions.

When the suggestions are ready, the `dataProvider` invokes a callback, where we update the current
suggestion list. We must be careful not to run into a race condition here, so before the update, we
check to see if the input has changed since we queried the suggestions.

Finally, we render the suggestion list:

<Tabs>

</Tabs>

![](@chat-sdk/react/v13/_assets/message-input-ui-emoji-autocomplete.png)

![](@chat-sdk/react/v13/_assets/message-input-ui-user-autocomplete.png)

![](@chat-sdk/react/v13/_assets/message-input-ui-command-autocomplete.png)

But we are not done yet. Displaying suggestions is only half of the puzzle. The second half is
reacting when the user selects one of the suggestions.

When the user selects a suggestion, we should do two things:

1. Call the `callback` for the current trigger. This will, for example, update the message payload
   with a user mention.
2. Examine the return value of the `output` for the selected suggestion, and update the message
   input text accordingly.

The `output` returns an object that looks something like this:

```json
{
  "text": "replacement text",
  "caretPosition": "next"
}
```

The `text` property tells us what to replace the current trigger with, and the `caretPosition`
property tells us where the cursor should end up after the update: either before or after the
inserted replacement.

Let's add a handler for the click event on the suggestion:

```js
const handleSuggestionClick = (item) => {
  if (autocompleteTriggers && trigger) {
    const { callback, output } = autocompleteTriggers[trigger[0]];
    callback?.(item);
    const replacement = output(item);

    if (replacement) {
      const start = text.indexOf(trigger);
      const end = start + trigger.length;
      const caretPosition =
        replacement.caretPosition === "start"
          ? start
          : start + replacement.text.length;

      const updatedText =
        text.slice(0, start) + replacement.text + text.slice(end);
      flushSync(() => setText(updatedText));
      inputRef.current?.focus();
      inputRef.current?.setSelectionRange(caretPosition, caretPosition);
    }
  }
};
```

And there you have it, the complete example that you can use as a starting point for your own
autocomplete implementation:

<Tabs>

</Tabs>


---

This page was last updated at 2026-04-21T09:53:42.229Z.

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