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
Live · keyboard ready
  1. Jasmine Brooks9:42 AM

    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

Enter to post

  • 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-input

Adds 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:

example.tsx
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.

PropTypeDefaultDescription
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.

PropTypeDefaultDescription
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.

PropTypeDefaultDescription
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.

PropTypeDefaultDescription
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.