Inputs

Mention Textarea

A textarea where typing @ opens a people menu and inserts a highlighted mention chip.

This is a WebberUI Pro component

The install command below needs a licence to run; without one it returns 401.

How to install Pro components →See the plans →

Typing @ instantly opens a keyboard-operable floating member menu; picking someone inserts a colour-highlighted @name chip, and onChange reports every member currently mentioned in the text — a good fit for comments, replies, and collaboration tools.

Bring this into your project

Run this command in your project folder. The CLI adds the source and dependencies; then follow the usage example below.

npx webberui@latest add "https://webberui.com/r/mention-textarea.en.json?t=<install token>"

New project or installation trouble? Start here →

Mention Textarea is a React @mention input component built with Tailwind CSS and Motion. It pairs a native textarea with a highlight mirror layer that scrolls in sync; typing @ opens a keyboard-navigable member menu, and picking someone inserts a colored @Name into the text.

Playground

Tune the props live — the code snippet updates as you go, so you can dial in the look you want before copying it.

4
6
Active edited usageDefault props
<MentionTextarea/>

The complete example includes the preview layout and data. Install the component first, then paste this file into your project.

When to use

  • Comment boxes and task discussions in project management or collaboration tools, using @ to notify teammates.
  • Internal notes in a help desk or ticketing system, tagging the colleague who should take over.
  • Feedback fields for design or document review.
  • Team announcement or chat message inputs.
  • Not a good fit: if you only need to pick several people from a list without writing them into text, use Tag Input.

Installation

npx webberui@latest add "https://webberui.com/r/mention-textarea.en.json?t=<install token>"

Or, once registries are configured in components.json, install it as @webberui/mention-textarea.

Usage

import {
  MentionTextarea,
  type MentionUser,
} from "@/components/ui/mention-textarea";

const users: MentionUser[] = [
  { id: "u1", name: "Lin Xiaomei", hue: 340, title: "Product Designer" },
  { id: "u2", name: "Chen Dawen", hue: 210, title: "Frontend Engineer" },
];

<MentionTextarea
  users={users}
  placeholder="Type a message, use @ to mention someone…"
  onChange={(value, mentions) => console.log(value, mentions)}
/>

Examples

Controlled mode and submitting a comment

Manage the content with value and onChange; the second argument of onChange is the deduplicated list of members currently mentioned, so you can send their ids to your backend for notifications.

"use client";

import * as React from "react";
import {
  MentionTextarea,
  type MentionUser,
} from "@/components/ui/mention-textarea";

export function CommentBox({ users, taskId }: { users: MentionUser[]; taskId: string }) {
  const [text, setText] = React.useState("");
  const [mentions, setMentions] = React.useState<MentionUser[]>([]);

  async function submit() {
    await fetch(`/api/tasks/${taskId}/comments`, {
      method: "POST",
      body: JSON.stringify({ text, mentionIds: mentions.map((u) => u.id) }),
    });
    setText("");
    setMentions([]);
  }

  return (
    <div className="space-y-2">
      <MentionTextarea
        users={users}
        value={text}
        onChange={(value, list) => {
          setText(value);
          setMentions(list);
        }}
        placeholder="Leave a comment, use @ to notify someone…"
      />
      <button onClick={submit} disabled={!text.trim()}>
        Post
      </button>
    </div>
  );
}

Building the member list from team data

hue (0–360) sets the avatar gradient and the mention highlight color. If your member data has no color field, derive a stable hue from the id so each person always gets the same color.

import { MentionTextarea, type MentionUser } from "@/components/ui/mention-textarea";

function hueFromId(id: string) {
  let h = 0;
  for (const ch of id) h = (h * 31 + ch.charCodeAt(0)) % 360;
  return h;
}

const members = [
  { id: "m_102", name: "Mia", role: "Product designer" },
  { id: "m_215", name: "Daniel", role: "Front-end engineer" },
  { id: "m_318", name: "Grace", role: "Project manager" },
];

const users: MentionUser[] = members.map((m) => ({
  id: m.id,
  name: m.name,
  title: m.role,
  hue: hueFromId(m.id),
}));

<MentionTextarea users={users} />;

Prefilled content and sizing

Any @Name in defaultValue that matches a member is shown highlighted right away; rows sets the textarea height and maxMenuItems limits how many people the menu shows at once.

<MentionTextarea
  users={users}
  defaultValue="@Grace can we confirm this requirement by next Monday?"
  rows={6}
  maxMenuItems={4}
  className="max-w-xl"
/>

Props

PropTypeDefaultDescription
usersMentionUser[]—The list of mentionable members ({ id, name, hue, title? }), used to filter the menu when @ is typed
valuestring—Text value in controlled mode, used together with onChange
defaultValuestring""Initial text value in uncontrolled mode
onChange(value: string, mentions: MentionUser[]) => void—Callback for content changes; the second argument is every member currently mentioned in the text
placeholderstring"Enter a message, using @ to mention a member…"Placeholder hint text of the input
rowsnumber4Number of rows the textarea shows
maxMenuItemsnumber6Maximum number of members the menu shows at once
classNamestring—Forwarded to the outermost container and merged with cn()

Accessibility

  • The input is marked role="combobox", aria-expanded, aria-autocomplete="list", the menu is role="listbox", and items are role="option" with aria-activedescendant pointing at the current option
  • Full keyboard support: ↑ / ↓ move between options, Enter or Tab inserts, Escape closes the menu
  • Menu items intercept the click with onMouseDown, so focus stays in the input throughout the selection
  • When the user has "reduce motion" enabled at the system level, the menu becomes a plain fade in / fade out and the spring displacement of the selection block is turned off
  • The highlight layer is an aria-hidden purely visual mirror; assistive technology still reads the textarea's original plain text content

FAQ

Does it work with the Next.js App Router?

Yes. The component file already starts with "use client", so import it directly from a Server Component page and pass the member list fetched on the server to users (MentionUser only contains strings and numbers).

Will typing with a Chinese or Japanese IME trigger the menu by accident?

No. The key handler checks isComposing first, so pressing Enter while the IME is composing only confirms the candidate and does not insert a member from the menu.

What format is the submitted content in?

value is plain text, with mentions written as @Name; the second argument of onChange gives the matching MentionUser objects. Mentions are matched by name, so member names should be unique.

Can members with spaces in their name be found?

The query after @ ends at the first whitespace and is capped at 20 characters, so only the part before the space is searchable; typing @Mary finds "Mary Lin", for example. Once picked, the full name is inserted and highlighted correctly.

Does it support keyboards and screen readers?

The textarea is a role="combobox" and the menu a role="listbox", with aria-activedescendant pointing at the current option; ↑ / ↓ move, Enter or Tab inserts and Escape closes. With "reduce motion" enabled, the menu only fades. Dependencies are motion, clsx and tailwind-merge.