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.