@danielivanovz/mention
Recipes

Custom rendering

Render-props inside <Mention.Item>, and the useMention() escape hatch for non-listbox layouts.

Most consumers can stay inside the compound API and pass children to <Mention.Item>. When you need full layout control — splitting the input from the popover, rendering the listbox as a grid, integrating with a different popover primitive — drop down to useMention().

Rich item content

<Mention.Item> accepts arbitrary children. Avatar + name + handle is the canonical layout:

<Mention.List>
  {(u) => (
    <Mention.Item value={u}>
      <img src={u.avatar} alt="" className="size-6 rounded-full" />
      <span className="font-medium">{u.name}</span>
      <span className="text-muted-foreground">@{u.username}</span>
    </Mention.Item>
  )}
</Mention.List>

The library applies role="option", id, hover handlers, and aria-selected on the option element. You don't need to wire any of those.

Grouping and dividers

The render-prop is just a function — interleave any non-Mention.Item JSX freely:

<Mention.List>
  {(u, i) => (
    <>
      {i === 0 || u.team !== items[i - 1].team ? (
        <div role="presentation" className="px-2 py-1 text-xs">
          {u.team}
        </div>
      ) : null}
      <Mention.Item value={u}>{u.name}</Mention.Item>
    </>
  )}
</Mention.List>

role="presentation" keeps the AT contract pristine — only <Mention.Item> is exposed as an option to screen readers.

Escape hatch — useMention()

When <Mention.Popover>'s default container doesn't fit (you're building a sticky bottom bar, or wrapping in your own design-system Popover), pull the props directly:

import { useMention } from "@danielivanovz/mention";

export function CustomMention() {
  const m = useMention<User>({
    items: users,
    getKey: (u) => u.id,
    getLabel: (u) => u.name,
    onSelect: (u) => console.log(u),
  });

  return (
    <div className="relative">
      <textarea {...m.getInputProps()} aria-label="Message" />
      {m.open ? (
        <ul {...m.getPopoverProps()} className="absolute mt-1 …">
          {m.items.map((u, i) => (
            <li
              key={u.id} // ← required; getItemProps doesn't include `key`
              {...m.getItemProps(u, i)}
            >
              {u.name}
            </li>
          ))}
        </ul>
      ) : null}
    </div>
  );
}

React 19 key warning

getItemProps deliberately does not include key — React 19's strict-mode dev warnings fire whenever key arrives via spread. Pass it explicitly:

{m.items.map((u, i) => (
  <li key={m.items[i] ? getKey(m.items[i]) : i} {...m.getItemProps(u, i)}>

  </li>
))}

The compound API (<Mention.List>) handles this internally — only escape-hatch consumers need to remember it.

Imperative control

Pass handleRef for open() / close() / commit() / direct textarea access:

const handle = useRef<MentionImperativeHandle<User>>(null);

<Mention.Root handleRef={handle} /* … */>

</Mention.Root>

// later:
handle.current?.commit(suggestedUser);
handle.current?.textarea?.focus();

Useful for tests, keyboard shortcuts ("⌘K opens the suggestion menu"), and integrating with command palettes.

On this page