Mentions

A textarea that opens a suggestion list after a trigger character, for @ mentions and anything else keyed the same way.

Accessibility

Mentions is a plain textarea with a menu beside it, linked for the keyboard only. What a screen reader needs to follow along is up to the page around the field.

What it renders

Two parts that are not linked to each other. The field is a plain <textarea>. The suggestion list is a separate menu in a portal, with role="menu" and role="menuitem" children.
There is no role="combobox" and no ARIA tying the two together. The field sets no aria-expanded, aria-controls, aria-activedescendant or aria-autocomplete, and focus stays in the textarea. The keyboard works, but a screen reader hears a textarea that seems to do nothing.
Treat the list as a shortcut. The field has to work for a reader who never uses it.

Announcing that suggestions appeared

Nothing announces the list. There is no live region, and the textarea does not reference the list, so a reader who types @ hears nothing while five names appear.
  • Add a polite live region beside the field and write a count into it from onSearch: “5 people”, then “no matches”. The count is what helps, not the names.
  • Debounce it. onSearch fires on every keystroke, so an undebounced region interrupts itself on every letter.
  • Say in the field's help text that typing @ offers names and the arrow keys move through them. It is the only way a reader finds out.

Keyboard

These keys only act while the list is open. When it is closed, the textarea behaves like any textarea.
  • Up and Down move the active option. The caret stays on its line.
  • Enter picks the active option. It does not add a newline or submit the form, and onPressEnter is not called while the list is open.
  • Escape closes the list and nothing else. The typed fragment stays, so the next character reopens the list. Deleting the trigger character is the only way to stop the search.
  • silent, which loading turns on, makes Enter do nothing, so a stale option cannot be picked while a request is out. For as long as the spinner shows, Enter has no effect.
The list has no tab stop, so Escape is the only way back to plain typing.

Labeling the field

id lands on the real <textarea>, so the usual labeling works.
  • A visible <label htmlFor>, or Form.Item, which wires the same pair for you.
  • aria-label when there is no room for visible text. It lands on the textarea.
  • A placeholder is not a label. It disappears at the first keystroke, which here is the one that starts the search.
  • The allowClear button has no text of its own. If you keep it, pass allowClear={{ clearIcon }} with an icon that carries an accessible name.

Reading order and mentions as text

While the list is open, a transparent copy of the text sits behind the field so the list can position itself. It has no aria-hidden, so the field's text can be read twice while the reader is searching.
  • The list renders outside the field, so its place in the reading order has nothing to do with the field's. Set getPopupContainer to move it closer. Either way, it is not announced.
  • A mention is plain text once inserted. There is no chip or link, only characters in a string. A reader cannot jump from mention to mention, count them, or hear that one was inserted.
  • Make up for it around the field. Render the posted comment's mentions as real links, and if a mention does something (notifies, assigns), say so in text near the field.

Contrast and motion

  • The active option is marked by a background fill alone. Check it against the popup's background in both modes: it is the only sign of where Enter will land.
  • Every color comes from the theme, so contrast depends on the theme. The kit's default theme is checked. A custom seed is not, and a palette picked for its hue can fall under 4.5:1 with no warning. The Tokens tab lists what Mentions reads.
  • status="error" only recolors the border. Pair it with a message through Form.Item or your own aria-describedby, because color alone is not accessible.
  • The popup fades and slides in. motion: false in your token file's seed turns that off for readers who ask for reduced motion. Put any motion you add behind motion-safe:.