Skip to content

Components · Collaboration

MentionPicker

@mention suggestions anchored to the caret, with async search, people and channel groups and listbox keys.

@rookcom/monarch-react/collaborationComponent

Comment field with people and channels

Type @ for people or # for channels.

Will notify: Ana Lopez

Usage examples

With a rich-text editor

Purpose

Suggests people and channels while someone types @ or # in a text field the product owns, and writes a stable mention token (@[Ana Lopez](u_ana)) when one is chosen, so the server can notify the right ids.

Use when

  • Comments, replies and descriptions in a plain textarea (useMentionTextarea).
  • A rich-text editor that reports its caret (useMentionPicker, which the wave 9 editor reuses).

Avoid when

  • Picking an assignee or owner from a field: use AssigneePicker or EntityPicker.
  • Choosing from a fixed list in a form: use Combobox.

Design principles

  • Focus never leaves the text field; the field points at the highlighted option, as in the APG combobox pattern.
  • Searching is the product’s: Monarch debounces, aborts stale requests and shows loading, empty and error states.
  • The token format is a contract: formatMentionToken and parseMentionTokens round-trip, and serialize lets a product keep its own format.
  • Typing prose is never hijacked: the list only opens at a word start and closes on a two-word query with no matches.

API reference

import { MentionPicker, findMentionQuery, formatMentionToken, getCaretRect, parseMentionTokens, useMentionPicker, useMentionTextarea } from '@rookcom/monarch-react/collaboration';
ExportKindRenderingRoot class
MentionPickercomponentclient.mn-mention-
findMentionQueryutilityclient–
formatMentionTokenutilityclient–
getCaretRectutilityclient–
parseMentionTokensutilityclient–
useMentionPickerhookclient–
useMentionTextareahookclient–

useMentionTextarea({ value, onChange, search, triggers?, serialize?, onMention?, debounceMs?, onKeyDown? }) returns textareaProps (spread on the <textarea>, including value and onChange) and pickerProps (spread on <MentionPicker>).

useMentionPicker({ search, onSelect, debounceMs? }) is the editor-agnostic core: call update(query, caretRect) as the caret moves, and handleKeyDown(event) first in your key handler; spread fieldProps on the editable element.

search(text, { trigger, signal }) returns items or a promise of items; abort on signal.

Helpers: findMentionQuery(value, caret, triggers), getCaretRect(field, index), formatMentionToken(item, trigger), parseMentionTokens(text, triggers).

Generated from the TypeScript declarations of packages/react/src/components/collaboration/mention-picker.

Properties

MentionPicker

The

PropTypeDescription
activeId requiredstring | null
anchor requiredMentionAnchorRect | nullThe caret rectangle in viewport coordinates. The list opens below it, or above when there is no room.
id requiredstringBase id; the listbox is `${id}-listbox`. From the hook.
items requiredreadonly MentionItem[]
onActiveChange required(id: string) => void
onSelect required(item: MentionItem) => void
open requiredboolean
status requiredMentionPickerStatus
childrenReactNode
classNamestring
containerHTMLElement | nullWhere to portal the list. Default `document.body`.
messagesPartial<MentionPickerMessages>
onRetry(() => void)Repeats a failed search.
queryMentionQuery | null
renderIcon((item: MentionItem) => ReactNode)Overrides the default avatar or `#` glyph before each option.

Variants

PropValuesDescription
triggers'['@']''['@', '#']'Characters that open the list. search receives the trigger, so # can search channels only.
MentionItem.kind'person''channel'People show an avatar, channels a # tile. Mixed results are split into labelled groups.

States

StateExposed asBehaviour
Searchingdata-status="loading"Spinner and “Searching…”.
Resultsdata-status="ready"Listbox with the first enabled option highlighted.
No matches–“No matches” for a one-word query; a two-word query with none closes the list.
Errordata-status="error"“Couldn’t load suggestions.” with Retry.
Disabled optiondata-disabledShown, skipped by arrow keys, cannot be chosen.

Interaction behaviour

  • Typing the trigger at the start of the text, after a space or after ( opens the list; [email protected] does not.
  • Each change searches after debounceMs (150 ms) and aborts the previous request.
  • Choosing replaces the query with the token and a space, then puts the caret after it.
  • The list opens below the caret and flips above it near the bottom of the viewport; it is kept inside the viewport horizontally.
  • Blurring the field or pressing Escape closes the list.

Keyboard support

KeysAction
Arrow Down / Arrow UpHighlight the next or previous enabled suggestion (wraps).
Enter / TabInsert the highlighted suggestion.
EscapeClose the list without leaving the field (and without closing a surrounding dialog).
Home / End, Arrow Left / RightMove the caret, as normal; moving it out of the query closes the list.

Keys reach your own onKeyDown whenever the list did not use them.

Accessibility

Role
listbox with option children; groups for people and channels
Accessible name
“Suggestions” (messages.label); groups are labelled “People” and “Channels”.
  • The field gets aria-autocomplete="list", aria-controls while the listbox is shown and aria-activedescendant for the highlighted option, so screen readers follow the highlight while focus stays in the field.
  • A polite status announces “Searching…”, “5 suggestions”, “No matches” or the error.
  • The textarea keeps its native textbox role: ARIA does not allow combobox on <textarea>.
  • Options use aria-selected for the highlight and aria-disabled for unavailable people.

Responsive behaviour

  • The list is at most 18rem wide and never wider than the viewport minus 1rem; it scrolls past 22rem or half the viewport height.
  • Long names and descriptions are truncated with an ellipsis.

Theme support

  • A raised surface (bg-raised, border-subtle, shadow-lg) like Monarch menus; the highlight is bg-muted.
  • Forced colours outline the highlighted option.

Do and don't

Do

  • Abort your request when signal fires.
  • Return people the viewer may mention; mark the ones without access disabled with a reason in description.
  • Resolve tokens to ids on the server too; never trust the label.

Don't

  • Do not move focus into the list.
  • Do not change the token format without updating every reader of stored text.
  • Do not return hundreds of results; cap the search at about 8–10.

Dependencies

KindDepends on
npm packagesreactreact-dom
Monarch componentsAvatar, Spinner

Testing coverage

  • Opens a listbox for @ and wires aria-autocomplete, aria-controls and aria-activedescendant; announces the count.
  • Arrow keys wrap and skip disabled options; Enter and Tab insert a token and leave the caret after it; clicks keep focus.
  • Escape closes without reaching outer handlers; other keys pass to the product.
  • People and channel groups; the # trigger.
  • Aborts the previous search; loading, error with Retry, no matches, two-word close.
  • Does not open inside an email address; closes on blur.
  • findMentionQuery, formatMentionToken and parseMentionTokens.
CheckCovered
Axe in jsdomYes
Server renderingYes
Browser suitesVisual baselines: /__gallery/mention-picker; Cross-browser axe: /components/mention-picker

Tests: packages/react/src/components/collaboration/mention-picker