MentionTextarea
Textarea that suggests people at the caret when you type @ (or another trigger) and reports the text plus a mentions array.
Code
import { Field, FieldLabel, type Mention, MentionTextarea } from "@fadymondy/nasaq/web";import { useState } from "react";const people = [ { id: "u1", name: "Sara Ali", description: "Design lead" }, { id: "u2", name: "Omar Nasser", description: "Engineer" },];export function Comment() { const [mentions, setMentions] = useState<Mention[]>([]); return ( <Field> <FieldLabel>Comment</FieldLabel> <MentionTextarea rows={4} suggestions={people} placeholder="Type @ to mention someone" onValueChange={(text, next) => setMentions(next)} /> <output>{mentions.length} mentioned</output> </Field> );}Collaboration · beta
Live examples and controls: MentionTextarea in the lab.
Install
npx shadcn@latest add https://docs.nasaqui.com/r/mention-textarea.jsonA Textarea that opens a list of suggestions at the caret when the user types a trigger character (@ by default). Choosing
an entry inserts @name. onValueChange returns the text and a mentions array with the id and the range of each mention, so
the server can notify the right people.
When to use
- Comments, notes and messages where people are referenced.
When not to use
- Picking a value from a list in a single-line control: use
combobox. - Free tags: use
tag-input.
Import
import { MentionTextarea } from "@fadymondy/nasaq/web";Quick start
import { Field, FieldLabel, type Mention, MentionTextarea } from "@fadymondy/nasaq/web";
import { useState } from "react";
const people = [
{ id: "u1", name: "Sara Ali", description: "Design lead" },
{ id: "u2", name: "Omar Nasser", description: "Engineer" },
];
export function Comment() {
const [mentions, setMentions] = useState<Mention[]>([]);
return (
<Field>
<FieldLabel>Comment</FieldLabel>
<MentionTextarea
rows={4}
suggestions={people}
placeholder="Type @ to mention someone"
onValueChange={(text, next) => setMentions(next)}
/>
<output>{mentions.length} mentioned</output>
</Field>
);
}Anatomy
div data-slot="mention-textarea" (relative wrapper)
textarea data-slot="textarea" (role="combobox")
ul data-slot="mention-list" (role="listbox", at the caret)
li data-slot="mention-option" (role="option", data-active)
div data-slot="mention-empty" (when nothing matches)API
| Prop | Type | Default | Description |
|---|---|---|---|
suggestions | MentionOption[] | required | { id, name, description?, avatar?, kind?, handle?, presence?, keywords? }. kind is person (default), team or group: the list is sectioned by kind, teams and groups show an icon, people a presence dot. handle and keywords are searchable. |
value / defaultValue | string | "" | Controlled or initial text. |
onValueChange | (value: string, mentions: Mention[]) => void | Text and every mention still in it, in text order. | |
defaultMentions | Mention[] | [] | Mentions that are already in value/defaultValue. Entries whose text no longer reads @name are dropped. |
trigger | string | "@" | The character (or string) that opens the list. Must start a word. |
maxSuggestions | number | 8 | Most rows shown. |
listLabel | string | "Mentions" / "الإشارات" | Listbox name. |
emptyLabel | string | "No matches" / "لا نتائج" | Shown when the query matches nobody. |
wrapperClassName | string | Class of the wrapper. className goes on the textarea. |
Other props go to the textarea (rows, placeholder, disabled, ...).
Mention = { id: string; name: string; start: number; end: number }; text.slice(start, end) is @name. Ranges follow the
text as the user edits before them, and a mention is dropped when the user edits inside it.
Examples
Arabic, with a hash trigger:
import { MentionTextarea } from "@fadymondy/nasaq/web";
export function Arabic() {
return (
<MentionTextarea
rows={3}
trigger="#"
suggestions={[
{ id: "t1", name: "التصميم" },
{ id: "t2", name: "الفوترة" },
]}
placeholder="اكتب # لإضافة وسم"
/>
);
}Accessibility
The textarea is an ARIA combobox (aria-autocomplete="list", aria-expanded, aria-controls, aria-activedescendant); the
list is a listbox of options. Focus never leaves the textarea. A polite status region announces the number of suggestions.
| Key | Action |
|---|---|
| Arrow Down / Up | Moves the highlight (wraps). |
| Enter / Tab | Inserts the highlighted mention. |
| Escape | Closes the list until the next trigger. |
| Any other key | Types; the list filters as you type. |
Without a match, or after Escape, Enter and Tab behave as in a normal textarea.
RTL & i18n
Filtering uses normalizeForSearch, so case, Arabic diacritics, tatweel and alef/yeh/teh-marbuta variants are ignored. The
list opens at the trigger's position, measured from the textarea's own direction, so it works right-to-left. A space is inserted
after the name (not part of the mention). Default strings are in English and Arabic; localise listLabel and emptyLabel for other languages.
Styling & tokens
Uses bg-popover, border-border, shadow-floating, bg-nq-selected (highlighted row), text-muted-foreground. Target
data-active on options. The list is positioned inside the wrapper; it does not flip above the field near the bottom of the
viewport.
Do / Don't
- Do keep
suggestionsshort or filter them on the server and pass the result. - Do send
mentions(ids) to the server rather than parsing the text. - Don't rely on the range for anything after the user edits without your
onValueChangeseeing it.
Related
Lab
https://docs.nasaqui.com/?path=/docs/components-collaboration-mention-textarea--docs
MembersManager
Members and roles of a workspace. A searchable table with an inline role select limited to the roles you can grant, an invite by email dialog, pending invites with resend and revoke, transfer of ownership and leave, with the last owner protected.
Activity Composer
Log a note, call, meeting or task, then read the history with open tasks first. Tasks tick off in place.