Mention

Let users @-mention tools in the composer with a keyboard-navigable popover picker and inline chips.

Getting Started

Add composer-mention

npx shadcn@latest add https://r.assistant-ui.com/composer-mention.json

This adds a /components/assistant-ui/composer-mention.tsx file with ComposerMentionPopover, ComposerMentionRoot, and DirectiveText.

Wrap the Composer

Wrap your composer with ComposerMentionPopover.Root and add <ComposerMentionPopover /> inside:

components/assistant-ui/thread.tsx
import { ComposerMentionPopover } from "@/components/assistant-ui/composer-mention";

const Composer = () => {
  return (
    <ComposerMentionPopover.Root>
      <ComposerPrimitive.Root>
        <ComposerPrimitive.Input placeholder="Type @ to mention a tool..." />
        <ComposerPrimitive.Send />
        <ComposerMentionPopover />
      </ComposerPrimitive.Root>
    </ComposerMentionPopover.Root>
  );
};

The popover automatically shows registered tools when the user types @.

Render Mentions in User Messages

Use DirectiveText as the Text component for user messages so mention directives render as inline chips instead of raw :tool[label] syntax:

components/assistant-ui/thread.tsx
import { DirectiveText } from "@/components/assistant-ui/composer-mention";

const UserMessage = () => {
  return (
    <MessagePrimitive.Root>
      <MessagePrimitive.Parts
        components={{
          Text: DirectiveText,
        }}
      />
    </MessagePrimitive.Root>
  );
};

DirectiveText renders plain text with mention chips. For assistant messages that contain markdown, keep using your markdown renderer (e.g. MarkdownText).

With Lexical Rich Editor

For inline mention chips in the composer (not just the popover), use LexicalComposerInput from @assistant-ui/react-lexical:

npm install @assistant-ui/react-lexical lexical @lexical/react

Replace ComposerPrimitive.Input with LexicalComposerInput:

components/assistant-ui/thread.tsx
import { LexicalComposerInput } from "@assistant-ui/react-lexical";

const Composer = () => {
  return (
    <ComposerMentionPopover.Root>
      <ComposerPrimitive.Root>
        <LexicalComposerInput placeholder="Type @ to mention a tool..." />
        <ComposerPrimitive.Send />
        <ComposerMentionPopover />
      </ComposerPrimitive.Root>
    </ComposerMentionPopover.Root>
  );
};

LexicalComposerInput auto-wires to MentionContext — no extra props needed. Selected mentions appear as inline chips that are treated as atomic units (select, delete, undo as a whole).

Custom Formatter

The default directive format is :type[label]{name=id}. To use a custom format, pass a formatter to both the mention root and the message renderer:

import { ComposerMentionPopover } from "@/components/assistant-ui/composer-mention";
import { createDirectiveText } from "@/components/assistant-ui/composer-mention";

const myFormatter = {
  serialize: (item) => `@${item.id}`,
  parse: (text) => [{ kind: "text", text }], // implement your parsing
};

// In composer (textarea path):
<ComposerMentionPopover.Root formatter={myFormatter}>
  ...
</ComposerMentionPopover.Root>

// In composer (Lexical path — also pass formatter):
<ComposerMentionPopover.Root formatter={myFormatter}>
  <ComposerPrimitive.Root>
    <LexicalComposerInput formatter={myFormatter} />
    ...
  </ComposerPrimitive.Root>
</ComposerMentionPopover.Root>

// In user messages:
const MyDirectiveText = createDirectiveText(myFormatter);
<MessagePrimitive.Parts components={{ Text: MyDirectiveText }} />

Keyboard Navigation

The mention popover supports full keyboard navigation out of the box:

KeyAction
ArrowDownHighlight next item
ArrowUpHighlight previous item
EnterSelect highlighted item / drill into category
EscapeClose popover
BackspaceGo back to categories (when query is empty)

Components

ComposerMentionPopover.Root

Wraps the composer with mention context and a tool mention adapter. Provides the @-trigger detection, keyboard navigation, and popover state.

PropTypeDefaultDescription
adapterUnstable_MentionAdapterTool adapterCustom mention adapter
triggerstring"@"Character(s) that open the popover
formatterUnstable_DirectiveFormatterDefaultCustom directive serializer/parser
formatLabel(name: string) => stringTitle caseFormat tool names for display
categoryLabelstring"Tools"Label for the tools category

ComposerMentionPopover

Pre-built popover containing categories and items lists. Only renders when the @ trigger is active.

DirectiveText

A TextMessagePartComponent that parses :type[label]{name=id} directives and renders them as styled inline chips.

createDirectiveText(formatter)

Factory function that creates a TextMessagePartComponent using a custom Unstable_DirectiveFormatter.