Mention input
A textarea with @people and #channel mentions that act as single tokens, with suggestions at the caret.
pnpm dlx shadcn@latest add @uiarc/mention-input
Onboarding v2 is live for 10% of new teams.
Marcus Johnson can you watch the error rate in #frontend today?Notified Marcus · Shared to #frontend
- Comment and reply boxes where people are tagged with @ and should be notified by id.
- Chat or task composers that link #channels or projects inline.
- Any text field whose stored value needs structured references alongside the plain text.
- Use textarea for plain notes with no references.
- Use tag-input when the tokens are a separate list rather than part of a sentence.
- Use rich-text-editor when the text also needs headings, lists, or links.
Installation
Add Mention input with the shadcn CLI, or copy the source by hand.
pnpm dlx shadcn@latest add @uiarc/mention-inputAdds the component and its local dependencies, and installs motion, lucide-react. First time? Add the @uiarc registry to components.json, or use the full URL:
import { useState } from "react";import { MentionInput, serializeMentions, type MentionValue } from "@/registry/components/mention-input/mention-input"; const people = [ { id: "u1", name: "Maya Chen", role: "Design lead" }, { id: "u2", name: "Theo Park", role: "Engineer" },];const channels = [{ id: "c1", name: "launch", description: "Release planning", members: 14 }]; export function CommentBox({ onPost }: { onPost: (body: string) => void }) { const [value, setValue] = useState<MentionValue>({ text: "", mentions: [] }); return ( <MentionInput aria-label="Comment" placeholder="Write a comment, @ to mention" people={people} channels={channels} value={value} onChange={setValue} submitOnEnter onSubmit={next => { onPost(serializeMentions(next)); setValue({ text: "", mentions: [] }); }} /> );}API reference
4 parts. The first is the root.
MentionInput
An autosizing textarea with @ people and # channel mentions. Mentions are atomic tokens, and the value carries plain text plus a structured mentions array with offsets.
valueMentionValue–Controlled value: { text, mentions }. Each mention has kind, id, label, start, and end offsets into text.defaultValueMentionValue–Initial value when uncontrolled. Defaults to empty text and no mentions.onChange(value: MentionValue) => void–Called on every edit with the new text and the mentions moved or dropped to match it.peopleMentionPerson[]–People offered after @. Each has id, name, and optional role and avatar. Leave it out to turn person mentions off.channelsMentionChannel[]–Channels offered after #. Each has id, name, and optional description and members count. Leave it out to turn channel mentions off.onMentionAdd(mention: Mention) => void–Called when a suggestion is chosen and becomes a mention.onSubmit(value: MentionValue) => void–Called on Enter when submitOnEnter is set and the text is not blank.submitOnEnterbooleanfalseEnter submits and Shift+Enter adds a line. Enter during IME composition never submits.placeholderstring–Placeholder text for the empty field.minRowsnumber1Starting height in rows.maxRowsnumber8The field grows to this many rows, then scrolls.placement"auto" | "top" | "bottom""auto"Where suggestions open. auto flips above the caret when there is less than 300px below it and more room above.maxSuggestionsnumber6Most suggestions shown at once, best matches first.disabledboolean–Disables the textarea and dims the field.namestring–Forwarded to the textarea. The form value is the plain text; use serializeMentions to keep ids.idstring–Forwarded to the textarea, for an external label.aria-labelstring–Accessible name when there is no visible label.aria-describedbystring–Id of help or error text for the textarea.classNamestring–Extra class on the root.MentionInputHandle
Methods on the ref.
focus() => void–Focuses the textarea.insert(text: string) => void–Inserts text at the caret as if typed, so it joins the native undo stack.openSuggestions(kind: MentionKind) => void–Types @ or # at the caret so suggestions open, adding a space first when needed. Useful for a mention button.clear() => void–Empties the text and mentions.textareaHTMLTextAreaElement | null–The underlying textarea.serializeMentions
Turns a value into storage friendly text, <@id> for people and <#id> for channels by default.
valueRequiredMentionValue–The value to serialize.format(mention: Mention) => string–Custom token for each mention, such as a markdown link.mentionText
Returns the visible text of a mention, @label or #label.
kindRequiredMentionKind–"person" or "channel".labelRequiredstring–The mention label.- @or#
- Opens people or channel suggestions after a space, line start, or opening bracket or quote.
- Arrow DownorArrow Up
- Moves through suggestions, wrapping at the ends.
- EnterorTab
- Turns the highlighted suggestion into a mention.
- Escape
- Dismisses suggestions for the current trigger without leaving the field.
- BackspaceorDelete
- Next to a mention, one press removes the whole token.
- Arrow LeftorArrow Right
- Steps over a mention as one unit.
- EnterorShift+Enter
- With submitOnEnter, Enter submits and Shift+Enter adds a line.
- The textarea is a combobox with aria-autocomplete="list", aria-expanded, aria-controls, and aria-activedescendant pointing at the highlighted option.
- Suggestions are a listbox labelled People or Channels, with options marked aria-selected.
- A polite live region announces the result count, such as 3 people or No matches.
- Mention tints come from an aria-hidden mirror behind a transparent textarea, so assistive tech reads the plain text.
- Choosing a suggestion types through execCommand insertText, so native undo keeps working.
- The field height springs to its content between minRows and maxRows.
- Suggestions pop in from the caret with a small scale and a snappy spring; the highlight glides between rows and the panel height springs as results change.
- Reduced motion jumps the height and highlight and fades the panel without scale or offset.
- Fills its container width. The suggestion panel is 272px wide, capped to the field width, and clamped so it never runs past the right edge.
- With placement auto the panel flips above the caret near the bottom of the viewport, which suits composers pinned to the bottom of a mobile screen.
- Options use pointer events and click, so touch taps choose a suggestion without a hover state.
- Each edit rebuilds a mirror of the text to draw token tints and reconciles mention offsets with a prefix and suffix diff; fine for messages, not for pages of text.
- Two ResizeObservers drive the field and panel height springs; ranking runs over the full people and channels arrays each keystroke, so pass a pre-filtered list past a few thousand entries.
Notes for AI
Give your coding assistant the Markdown reference instead of screenshots.
- Choose it for comment boxes, message composers, and task descriptions that tag people or channels. Use textarea for plain multi-line text and tag-input for a list of chips outside the text.
- Store serializeMentions(value) or the mentions array, not only value.text, so renamed people still resolve by id.
- Filter people on the server by passing a fresh people array per keystroke; ranking is prefix, then word start, then substring, then role or description.
- Hook a toolbar @ button to ref.openSuggestions("person").
The full library index for assistants is at /llms.txt.