Skip to content
WANCRAFT

F.146Forms & Inputs

Mention Input

A textarea with @mention and #tag autocomplete at the caret, highlighted tokens and whole-token delete.

NewReactMotionTailwindadvanced

Overview

Mention Input is a plain textarea that understands @mentions and, optionally, #tags. Typing a trigger at the start of a word opens a suggestions listbox right next to the caret, positioned with the mirror-div technique (an invisible copy of the textarea measures where the character sits). Arrow keys move through suggestions via aria-activedescendant while focus stays in the field; Enter or Tab inserts the token. Mentions are highlighted by a pixel-aligned layer behind a transparent-text textarea, a deliberate choice over contentEditable: native editing, selection, spellcheck, IME composition, autofill and undo keep working, and the value stays plain text. Backspace next to a mention removes the whole token, edits inside one turn it back into plain text, and the value API returns the text plus a mentions array with ids and ranges. Suggestions can be filtered locally or loaded through a debounced, cancellable async onSearch.

01Installation

Method 01 · Registry

CLI verified

Uses the shadcn CLI in a project initialised with shadcn init. Installs the file to components/ui/mention-input.tsx and adds its dependencies.

npx shadcn@latest add https://wancraft.my/r/mention-input.json

Method 02 · Copy and paste

  1. 1. Install the dependencies below.
  2. 2. Copy the source from the Code tab into components/ui/mention-input.tsx.
  3. 3. Import it and adjust props.
npm install motion
Dependencies
motion
Peer
react ≥ 18, tailwindcss ≥ 4
Added
13 Oct 2026
Updated
—

02Usage

The default configuration. Change properties above to generate a tailored example in the Usage tab.

example.tsx
import { MentionInput, type MentionItem } from "@/components/ui/mention-input";

export function MentionInputExample() {
  const users: MentionItem[] = [
    { id: "u1", label: "Ada Lovelace", description: "@ada" },
    { id: "u2", label: "Grace Hopper", description: "@grace" },
    { id: "u3", label: "Alan Turing", description: "@alan" },
  ];

  return (
    <div className="w-full max-w-lg">
      <MentionInput
        label="Comment"
        users={users}
        onChange={({ text, mentions }) => console.log(text, mentions)}
      />
    </div>
  );
}

03Props

PropTypeDefaultDescription
value / defaultValueMentionValue—{ text: string; mentions: Mention[] } — controlled value, or the initial value when uncontrolled.
onChange(value: MentionValue) => void—Called after every edit with the plain text and the mentions ({ id, label, trigger, start, end }).
usersMentionItem[][]People offered after @ ({ id, label, description? }), filtered locally.
tagsMentionItem[][]Tags offered after #, filtered locally.
onSearch(query, trigger, signal) => Promise<MentionItem[]>—Async suggestions; replaces local filtering. The AbortSignal fires when a newer query supersedes it.
allowTagsbooleantrueEnable # tags alongside @ mentions.
debouncenumber150Delay in ms before onSearch runs.
labelReactNode"Message"Visible <label> for the textarea.
hintReactNode—Helper text linked with aria-describedby.
placeholderstring"Write a comment — type @ to mention someone"Textarea placeholder.
rowsnumber4Visible rows.
maxLengthnumber—Optional character limit with a counter.
accentstring"#FF7448"Mention highlight, list highlight and focus colour.
disabledbooleanfalseDisables the field.

04Features

  • 01@mentions and optional #tags detected at the caret, only at the start of a word
  • 02Suggestions positioned next to the caret using mirror-div coordinates, clamped to the field
  • 03Highlighted tokens via a pixel-aligned overlay behind a transparent-text native textarea
  • 04Backspace/Delete next to a mention removes the whole token; editing inside one turns it into plain text
  • 05Value API: plain text plus a mentions array with ids and character ranges
  • 06Local filtering or debounced async onSearch with AbortSignal cancellation, loading and error states
motion · springinput · keyboardinput · focusinput · click

05Accessibility

  • A real <textarea> with a visible <label>, so typing, selection, spellcheck, IME and screen-reader editing behave natively.
  • Combobox-like pattern: the textarea sets aria-autocomplete="list", aria-controls and aria-activedescendant while the role="listbox" of role="option" suggestions is open; focus never leaves the field.
  • ArrowUp/ArrowDown move, Enter or Tab inserts, Escape dismisses the suggestions for that trigger.
  • Result counts, insertions and token removals are announced in a polite live region; the highlight layer is aria-hidden.
  • prefers-reduced-motion removes the popup spring.

06Limitations

  • The overlay relies on the textarea and the layer sharing identical type metrics; keep custom font, size and padding changes on both (they share one class string in the source).
  • The textarea keeps its implicit textbox role rather than role="combobox", because ARIA does not allow a multi-line combobox; some screen readers announce the suggestions only through the live region.