Mention Input

Pro

Contenteditable input with @mention autocomplete — a cursor-anchored suggestion dropdown, keyboard navigation and styled mention chips. Supports single- or multi-line entry and multi-trigger mode (@ people, # topics, / commands).

</>sv-mention mentioninputautocompletemulti-trigger
import { SvMentionComponent } from '@styloviz-pro/forms';

Demo & usage

Basic usage

Pro

Usage example — open the gallery for the full interactive demo.

Pro

An interactive demo for this component lives in the gallery.

Open live demo

More examples

Single-line with # trigger

Pro

Multi-trigger — @ people, # topics, / commands (Pro)

Pro

Pass a triggers array; each entry has its own char, suggestion set and optional chip colour. triggerSelected tells you which trigger fired.

When to use

  • Letting users reference people or entities inline (comments, chat, task notes).
  • You want @-style autocomplete that anchors to the caret.
  • For a standalone tag field (not inline in prose), use Tag Input.

Async states

  • loading
  • error
  • retry

Inputs

NameTypeDefaultDescription
suggestionsMentionSuggestion[][]List of mention candidates.
triggerCharstring'@'Character that triggers the dropdown (single-trigger mode).
triggersreadonly MentionTrigger[][]Multi-trigger configuration (Pro). When non-empty, this takes precedence over `triggerChar`/`suggestions`: each entry defines a trigger character, its own suggestion set, and optional chip styling.
placeholderstring'Type @ to mention someone…'Placeholder text.
multiLinebooleanfalseMulti-line mode (expand with content).
maxSuggestionsnumber8Maximum items shown in the dropdown.
disabledbooleanfalseDisabled state.
valuestring''Plain-text content. Two-way bindable with `[(value)]`.
serverSidebooleanfalseLet the server do the matching. The supplied `suggestions` are shown exactly as given.
debounceMsnumber200Delay before `queryChange` fires, in ms. `0` emits immediately.
minCharsnumber0Characters after the trigger before a lookup is sent. `0` looks up as soon as the trigger is typed, which is what most directories want.
loadingbooleanfalseTrue while suggestions are being fetched.
errorSvDataError | string | nullnullFailure loading suggestions, rendered inside the panel.
noResultsTextstring'No matches'Copy when the lookup returned nothing.
errorMessagestring''Client-side validation message.
serverErrorSvServerErrornullA rejection reported by the server about this value. Self-expiring.

Outputs

EventPayload typeDescription
mentionSelectedMentionSuggestionEmitted when a mention is selected.
triggerSelected{ trigger: string; suggestion: MentionSuggestion }Emitted alongside `mentionSelected`, carrying the trigger character that produced the mention — useful in multi-trigger mode to tell `@`, `#` and `/` selections apart.
valueChangestringEmitted when the text content changes (pairs with `value` for `[(value)]`).
queryChangeMentionQueryEventThe text typed after the trigger changed — fetch matching people. Carries the trigger character too, because a component with several triggers (`@` for people, `#` for channels) queries different endpoints. Debounced by `debounceMs` and never emitted twice for the same pair.
retryvoidThe host should re-run the failed lookup.
mentionsChangeSvMentionStateThe mentions actually in the field, and the ones that no longer say what they mean. AN OUTPUT RATHER THAN ONLY A METHOD, BECAUSE THIS COMPONENT'S VALUE CARRIES NO IDS. `value` is plain text; the ids live only in the DOM. A host binding `[(value)]` and `(mentionSelected)` therefore keeps its recipient list BESIDE the document — and that list is wrong the moment somebody deletes a chip, pastes over one, or edits its text. There is no reconciling it from the value, so the component has to volunteer the truth. `sv-rich-text-editor` exposes methods instead and can afford to: its value is the markup, so the ids are in the thing the host already persists. Emitted only when the state CHANGES, not per keystroke. A mention field fires `input` on every character, and a host that re-runs a permission check each time would be doing it thousands of times per message.

Wire it to your API

This component is headless about transport: it renders what you give it and reports what the user asks for. Nothing below requires @styloviz/data — it just saves you writing the debounce, the request guard and the retry policy by hand.

Template

Pro

Bound to the names this component actually exposes.

Host class

Pro

One controller handles debounce, request-guarding, cancellation and retry.

  • `queryChange` carries an `SvDataQuery` — page, page size, sorts, filters, search and cursor in one object. One handler covers every way the user can change what they are asking for.
  • `serverSide` switches **off** the built-in filtering, sorting and paging. Leave it false and the component works entirely from the array you pass — which is what you want for a few hundred rows.
  • `debounceMs` (default 300) collapses a burst of typing into one request. Set it to 0 only when the query is driven by clicks.
  • Normalize failures with `toSvDataError()` from `@styloviz/core`. It derives `retryable` from the status, so a 404 does not get a "Try again" button that can only fail again.

Full walkthrough: Connecting to a backend.

Customization & theming

  • Provide `suggestions` (`MentionSuggestion[]`) and change the `triggerChar` (e.g. `@`, `#`, `/`).
  • For several inline autocompletes in one field, pass `triggers` (`MentionTrigger[]`) — each has its own `char`, `suggestions`, optional `chipClass` and `label`; the nearest trigger to the caret wins.
  • Toggle `multiLine` for a textarea-style field and cap results with `maxSuggestions`.
  • Listen to `mentionSelected` for the chosen entity, `triggerSelected` for the `{ trigger, suggestion }` pair, and `valueChange` for the raw text.
  • Bind the plain-text content with `[(value)]`, or use `[formControl]` / `[(ngModel)]` — the editor implements `ControlValueAccessor` and hydrates programmatic values when not focused.
  • Responsive: the suggestion popup is anchored to the caret and its width is capped to the viewport, so it stays fully on-screen on narrow devices.

Best practices

  • Do: Cap suggestions with `maxSuggestions` so the dropdown stays scannable.
  • Do: Resolve `mentionSelected` to a stable id, not just the display name.
  • Tip: Change `triggerChar` (@, #, /) to match the entity type being referenced.

Accessibility

  • The editor is `role="textbox"` with `aria-autocomplete="list"`, `aria-expanded`, `aria-controls` and `aria-activedescendant` tracking the highlighted suggestion (ids are generated per instance, so multiple fields never collide).
  • Arrow keys move, Enter/Tab insert, Esc closes without losing the text; the suggestion popup is a `role="listbox"` of `role="option"` rows.
  • Inserted mentions render as styled chips kept in the text flow for screen readers.

Feedback on Mention Input

Spotted a gap in this page, or something that didn't work as described? Comments are GitHub Discussions in the public styloviz/community repo, so posting needs a GitHub account.

Loads giscus.app and GitHub — nothing is fetched until you click.