A growing textarea with @mentions and any other trigger you give it, such as #tags: typing a trigger opens a suggestion list anchored at the caret, filtered as you type with accent-insensitive matching and the matched letters in bold, picked with the arrows and Enter or Tab. Picked mentions are drawn as tinted chips exactly in line with the text, stepped over by the arrow keys and removed whole by Backspace. The textarea stays the source of truth, the value is plain markup (@[Name](id)) with parse and serialize helpers, suggestions can come from a list or an async function, and the browser's undo history, IME input, forms and combobox semantics all keep working.
The import and the props worth knowing about, in one place.
import { MentionInput } from "@/components/beste/component/mention-input";
<MentionInput
placeholder="Write a comment"
triggers={[
{ char: "@", items: people }, // { id, label, description?, avatar? }[]
{ char: "#", items: (query) => fetch(`/api/tags?q=${query}`).then((r) => r.json()) },
]}
onValueChange={(markup, { text, mentions }) => console.log(markup, mentions)}
onSubmit={(markup) => console.log("Send", markup)} // Mod+Enter by default
submitKey="mod+enter" // or "enter", with Shift+Enter for a new line
rows={2}
maxRows={8}
name="comment" // submits the markup with a form
/>Every one of these is in the component already. They are listed because a props table cannot mention a gesture, so nothing else on this page can tell you they exist.
| @ or # | Opens the suggestion list at the caret, filtered by what follows. |
|---|---|
| Up / Down | Moves through the suggestions. |
| Enter or Tab | Inserts the highlighted suggestion as a mention. |
| Escape | Closes the list until the caret leaves this trigger. |
| Backspace / Delete | Removes a whole mention next to the caret. |
| Left / Right | Steps over a mention in one press. |
| Mod + Enter | Calls onSubmit with the markup, when one is given. |
Read from the component's own type, so this cannot drift from what it accepts.
| Prop | Type | Default | Description |
|---|---|---|---|
value | string | — | Controlled value, as markup: `@[Name](id)`. |
defaultValue | string | — | |
onValueChange | (value: string, detail: MentionChange) => void | — | |
triggers | MentionTrigger[] | — | Triggers and what they offer. @defaultValue one "@" trigger with no items |
placeholder | string | — | |
rows | number | — | Lines the field opens at. @defaultValue 2 |
maxRows | number | — | Lines it grows to before scrolling. @defaultValue 8 |
onSubmit | (value: string) => void | — | Called with the markup on Enter or Mod+Enter, as `submitKey` says. |
submitKey | "enter" | "mod+enter" | "mod+enter" | |
limit | number | — | Most suggestions shown at once. @defaultValue 6 |
disabled | boolean | — | |
name | string | — | Submits the markup with a form. |
tone | "muted" | "outline" | "ghost" | "muted" | |
size | "sm" | "default" | "lg" | "default" | |
className | string | — | |
aria-label | string | — |