useMentions Hook

The headless hook. Full control over rendering.

Angular doesn't use a hook — its headless primitive is MentionsControllerBridge, which wraps the same core MentionController and exposes its state as a signal. The options below map to MentionControllerOptions; the return values map to bridge.state() and bridge.controller.*.

Import

import { useMentions } from "@skyastrall/mentions-react";
import { useMentions } from "@skyastrall/mentions-vue";
import { useMentions } from "@skyastrall/mentions-svelte";
import { MentionsControllerBridge } from "@skyastrall/mentions-angular";

// new MentionsControllerBridge(options, destroyRef)
//   .state()      -> signal of MentionState (status, items, query, ...)
//   .controller   -> MentionController (handleInputChange, handleKeyDown, ...)

Options

Prop Type Default Description
triggers TriggerConfig[] — Trigger configurations (required)
value string — Controlled markup value
defaultValue string — Initial markup for uncontrolled mode
onChange (markup, plainText) => void — Called on every content change
onSelect (item, trigger) => void — Called when a mention is selected
onRemove (item, trigger) => void — Called when a mention is removed
onQueryChange (query, trigger) => void — Called when the search query changes
onOpen (trigger) => void — Called when the dropdown opens
onClose () => void — Called when the dropdown closes
onError (error) => void — Called on async fetch errors
ghostText string — Dimmed inline completion text
onAcceptGhostText () => void — Called when ghost text is accepted via Tab

Return Value

Prop Type Default Description
editorRef RefObject<HTMLDivElement | null> — Attach to your contenteditable div
inputProps object — ARIA + event handlers — spread onto the editor div
listProps object — ARIA attributes for the suggestion list
getItemProps (index) => object — ARIA + handlers for each item
isOpen boolean — Whether the dropdown is visible
isLoading boolean — Whether an async fetch is in progress
items MentionItem[] — Current suggestion items
query string — Current search query text
highlightedIndex number — Highlighted item (-1 = none)
activeTrigger string | null — Active trigger character
caretPosition CaretPosition | null — Caret viewport coordinates
state MentionState — Full state machine state (status, isComposing, etc.)
markup string — Current markup string
plainText string — Current plain text
mentions MentionItem[] — All active mentions
ghostText string | undefined — Current ghost text value
handleInput () => void — Wire to onInput on your contenteditable div
clear () => void — Clears all content
focus () => void — Focuses the editor
insertTrigger (trigger) => void — Inserts trigger and opens dropdown

inputProps includes ARIA combobox attributes (role="combobox", aria-expanded, aria-activedescendant, etc.) plus onKeyDown, onBlur, onCompositionStart, and onCompositionEnd handlers. Spread it onto your contenteditable div.

Basic Example

import { useMentions } from "@skyastrall/mentions-react";

const users = [
  { id: "1", label: "Alice" },
  { id: "2", label: "Bob" },
];

function CustomEditor() {
  const {
    editorRef, inputProps, listProps, getItemProps,
    isOpen, items, highlightedIndex, caretPosition,
  } = useMentions({
    triggers: [{ char: "@", data: users }],
    onChange: (markup, plainText) => console.log(markup),
  });

  return (
    <div data-mentions="" style={{ position: "relative" }}>
      <div
        ref={editorRef}
        contentEditable="plaintext-only"
        data-mentions-editor=""
        {...inputProps}
        style={{ outline: "none", whiteSpace: "pre-wrap", padding: 12 }}
      />
      {isOpen && items.length > 0 && (
        <ul
          {...listProps}
          style={{
            position: "fixed",
            top: (caretPosition?.top ?? 0) + (caretPosition?.height ?? 0) + 4,
            left: caretPosition?.left ?? 0,
          }}
        >
          {items.map((item, i) => (
            <li
              key={item.id}
              {...getItemProps(i)}
              style={{
                padding: "8px 12px",
                background: i === highlightedIndex ? "#f1f5f9" : "white",
                cursor: "pointer",
              }}
            >
              {item.label}
            </li>
          ))}
        </ul>
      )}
    </div>
  );
}

The editor needs data-mentions="" on a parent and data-mentions-editor="" on itself for blur handling and placeholder CSS to work correctly.