@assistant-ui/mcp-docs-server 0.1.27 → 0.1.29

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (39) hide show
  1. package/.docs/organized/code-examples/waterfall.md +6 -6
  2. package/.docs/organized/code-examples/with-a2a.md +7 -7
  3. package/.docs/organized/code-examples/with-ag-ui.md +8 -8
  4. package/.docs/organized/code-examples/with-ai-sdk-v6.md +9 -9
  5. package/.docs/organized/code-examples/with-artifacts.md +9 -9
  6. package/.docs/organized/code-examples/with-assistant-transport.md +7 -7
  7. package/.docs/organized/code-examples/with-chain-of-thought.md +9 -9
  8. package/.docs/organized/code-examples/with-cloud-standalone.md +9 -9
  9. package/.docs/organized/code-examples/with-cloud.md +9 -9
  10. package/.docs/organized/code-examples/with-custom-thread-list.md +9 -9
  11. package/.docs/organized/code-examples/with-elevenlabs-conversational.md +11 -11
  12. package/.docs/organized/code-examples/with-elevenlabs-scribe.md +11 -11
  13. package/.docs/organized/code-examples/with-expo.md +17 -17
  14. package/.docs/organized/code-examples/with-external-store.md +7 -7
  15. package/.docs/organized/code-examples/with-ffmpeg.md +9 -9
  16. package/.docs/organized/code-examples/with-generative-ui.md +841 -0
  17. package/.docs/organized/code-examples/with-google-adk.md +7 -7
  18. package/.docs/organized/code-examples/with-heat-graph.md +6 -6
  19. package/.docs/organized/code-examples/with-interactables.md +9 -9
  20. package/.docs/organized/code-examples/with-langgraph.md +9 -9
  21. package/.docs/organized/code-examples/with-livekit.md +50 -14
  22. package/.docs/organized/code-examples/with-opencode.md +2394 -0
  23. package/.docs/organized/code-examples/with-parent-id-grouping.md +8 -8
  24. package/.docs/organized/code-examples/with-react-hook-form.md +10 -10
  25. package/.docs/organized/code-examples/with-react-ink.md +3 -3
  26. package/.docs/organized/code-examples/with-react-router.md +8 -8
  27. package/.docs/organized/code-examples/with-store.md +6 -6
  28. package/.docs/organized/code-examples/with-tanstack.md +10 -10
  29. package/.docs/organized/code-examples/with-tap-runtime.md +7 -7
  30. package/.docs/raw/docs/(docs)/guides/mentions.mdx +406 -0
  31. package/.docs/raw/docs/(docs)/guides/slash-commands.mdx +275 -0
  32. package/.docs/raw/docs/(docs)/guides/voice.mdx +10 -3
  33. package/.docs/raw/docs/primitives/composer.mdx +27 -4
  34. package/.docs/raw/docs/runtimes/custom/custom-thread-list.mdx +2 -2
  35. package/.docs/raw/docs/runtimes/custom/external-store.mdx +38 -0
  36. package/.docs/raw/docs/runtimes/custom/local.mdx +65 -18
  37. package/.docs/raw/docs/runtimes/google-adk/index.mdx +62 -0
  38. package/.docs/raw/docs/runtimes/langgraph/index.mdx +137 -1
  39. package/package.json +4 -4
@@ -474,28 +474,28 @@ export default nextConfig;
474
474
  "start": "next start"
475
475
  },
476
476
  "dependencies": {
477
- "@ai-sdk/openai": "^3.0.50",
477
+ "@ai-sdk/openai": "^3.0.52",
478
478
  "@assistant-ui/react": "workspace:*",
479
479
  "@assistant-ui/react-markdown": "workspace:*",
480
480
  "@assistant-ui/ui": "workspace:*",
481
481
  "class-variance-authority": "^0.7.1",
482
482
  "clsx": "^2.1.1",
483
- "lucide-react": "^1.7.0",
484
- "next": "^16.2.2",
485
- "react": "^19.2.4",
486
- "react-dom": "^19.2.4",
483
+ "lucide-react": "^1.8.0",
484
+ "next": "^16.2.3",
485
+ "react": "^19.2.5",
486
+ "react-dom": "^19.2.5",
487
487
  "tailwind-merge": "^3.5.0"
488
488
  },
489
489
  "devDependencies": {
490
490
  "@assistant-ui/x-buildutils": "workspace:*",
491
491
  "@tailwindcss/postcss": "^4.2.2",
492
- "@types/node": "^25.5.0",
492
+ "@types/node": "^25.6.0",
493
493
  "@types/react": "^19.2.14",
494
494
  "@types/react-dom": "^19.2.3",
495
- "postcss": "^8.5.8",
495
+ "postcss": "^8.5.9",
496
496
  "tailwindcss": "^4.2.2",
497
497
  "tw-animate-css": "^1.4.0",
498
- "typescript": "5.9.3"
498
+ "typescript": "^6.0.2"
499
499
  }
500
500
  }
501
501
 
@@ -537,32 +537,32 @@ export default nextConfig;
537
537
  "start": "next start"
538
538
  },
539
539
  "dependencies": {
540
- "@ai-sdk/openai": "^3.0.50",
540
+ "@ai-sdk/openai": "^3.0.52",
541
541
  "@assistant-ui/react": "workspace:*",
542
542
  "@assistant-ui/react-ai-sdk": "workspace:*",
543
543
  "@assistant-ui/react-hook-form": "workspace:*",
544
544
  "@assistant-ui/react-markdown": "workspace:*",
545
545
  "@assistant-ui/ui": "workspace:*",
546
- "ai": "^6.0.144",
546
+ "ai": "^6.0.159",
547
547
  "class-variance-authority": "^0.7.1",
548
548
  "clsx": "^2.1.1",
549
- "lucide-react": "^1.7.0",
550
- "next": "^16.2.2",
551
- "react": "^19.2.4",
552
- "react-dom": "^19.2.4",
553
- "react-hook-form": "^7.72.0",
549
+ "lucide-react": "^1.8.0",
550
+ "next": "^16.2.3",
551
+ "react": "^19.2.5",
552
+ "react-dom": "^19.2.5",
553
+ "react-hook-form": "^7.72.1",
554
554
  "tailwind-merge": "^3.5.0"
555
555
  },
556
556
  "devDependencies": {
557
557
  "@assistant-ui/x-buildutils": "workspace:*",
558
558
  "@tailwindcss/postcss": "^4.2.2",
559
- "@types/node": "^25.5.0",
559
+ "@types/node": "^25.6.0",
560
560
  "@types/react": "^19.2.14",
561
561
  "@types/react-dom": "^19.2.3",
562
- "postcss": "^8.5.8",
562
+ "postcss": "^8.5.9",
563
563
  "tailwindcss": "^4.2.2",
564
564
  "tw-animate-css": "^1.4.0",
565
- "typescript": "5.9.3"
565
+ "typescript": "^6.0.2"
566
566
  }
567
567
  }
568
568
 
@@ -16,14 +16,14 @@
16
16
  "dependencies": {
17
17
  "@assistant-ui/react-ink": "workspace:*",
18
18
  "@assistant-ui/react-ink-markdown": "workspace:*",
19
- "ink": "^6.8.0",
20
- "react": "^19.2.4"
19
+ "ink": "^7.0.0",
20
+ "react": "^19.2.5"
21
21
  },
22
22
  "devDependencies": {
23
23
  "@types/react": "^19.2.14",
24
24
  "esbuild": "^0.28.0",
25
25
  "tsx": "^4.21.0",
26
- "typescript": "5.9.3"
26
+ "typescript": "^6.0.2"
27
27
  }
28
28
  }
29
29
 
@@ -777,11 +777,11 @@ export default function Home() {
777
777
  "class-variance-authority": "^0.7.1",
778
778
  "clsx": "^2.1.1",
779
779
  "eventsource-parser": "^3.0.6",
780
- "isbot": "^5.1.37",
781
- "lucide-react": "^1.7.0",
782
- "openai": "^6.33.0",
783
- "react": "^19.2.4",
784
- "react-dom": "^19.2.4",
780
+ "isbot": "^5.1.38",
781
+ "lucide-react": "^1.8.0",
782
+ "openai": "^6.34.0",
783
+ "react": "^19.2.5",
784
+ "react-dom": "^19.2.5",
785
785
  "react-router": "^7.14.0",
786
786
  "remark-gfm": "^4.0.1",
787
787
  "tailwind-merge": "^3.5.0"
@@ -789,13 +789,13 @@ export default function Home() {
789
789
  "devDependencies": {
790
790
  "@react-router/dev": "^7.14.0",
791
791
  "@tailwindcss/vite": "^4.2.2",
792
- "@types/node": "^25.5.0",
792
+ "@types/node": "^25.6.0",
793
793
  "@types/react": "^19.2.14",
794
794
  "@types/react-dom": "^19.2.3",
795
795
  "tailwindcss": "^4.2.2",
796
796
  "tw-animate-css": "^1.4.0",
797
- "typescript": "5.9.3",
798
- "vite": "^8.0.3",
797
+ "typescript": "^6.0.2",
798
+ "vite": "^8.0.8",
799
799
  "vite-tsconfig-paths": "^6.1.1"
800
800
  }
801
801
  }
@@ -488,20 +488,20 @@ export default nextConfig;
488
488
  "dependencies": {
489
489
  "@assistant-ui/store": "workspace:*",
490
490
  "@assistant-ui/tap": "workspace:*",
491
- "next": "^16.2.2",
492
- "react": "^19.2.4",
493
- "react-dom": "^19.2.4"
491
+ "next": "^16.2.3",
492
+ "react": "^19.2.5",
493
+ "react-dom": "^19.2.5"
494
494
  },
495
495
  "devDependencies": {
496
496
  "@assistant-ui/x-buildutils": "workspace:*",
497
497
  "@tailwindcss/postcss": "^4.2.2",
498
- "@types/node": "^25.5.0",
498
+ "@types/node": "^25.6.0",
499
499
  "@types/react": "^19.2.14",
500
500
  "@types/react-dom": "^19.2.3",
501
- "postcss": "^8.5.8",
501
+ "postcss": "^8.5.9",
502
502
  "tailwindcss": "^4.2.2",
503
503
  "tw-animate-css": "^1.4.0",
504
- "typescript": "5.9.3"
504
+ "typescript": "^6.0.2"
505
505
  }
506
506
  }
507
507
 
@@ -65,15 +65,15 @@
65
65
  "@assistant-ui/react": "workspace:*",
66
66
  "@assistant-ui/react-markdown": "workspace:*",
67
67
  "@tailwindcss/vite": "^4.2.2",
68
- "@tanstack/react-router": "^1.168.10",
69
- "@tanstack/react-start": "^1.167.16",
68
+ "@tanstack/react-router": "^1.168.19",
69
+ "@tanstack/react-start": "^1.167.35",
70
70
  "class-variance-authority": "^0.7.1",
71
71
  "clsx": "^2.1.1",
72
- "lucide-react": "^1.7.0",
73
- "nitro": "^3.0.1-alpha.2",
74
- "openai": "^6.33.0",
75
- "react": "^19.2.4",
76
- "react-dom": "^19.2.4",
72
+ "lucide-react": "^1.8.0",
73
+ "nitro": "^3.0.260311-beta",
74
+ "openai": "^6.34.0",
75
+ "react": "^19.2.5",
76
+ "react-dom": "^19.2.5",
77
77
  "remark-gfm": "^4.0.1",
78
78
  "tailwind-merge": "^3.5.0",
79
79
  "tailwindcss": "^4.2.2",
@@ -81,12 +81,12 @@
81
81
  },
82
82
  "devDependencies": {
83
83
  "@assistant-ui/x-buildutils": "workspace:*",
84
- "@types/node": "^25.5.0",
84
+ "@types/node": "^25.6.0",
85
85
  "@types/react": "^19.2.14",
86
86
  "@types/react-dom": "^19.2.3",
87
87
  "@vitejs/plugin-react": "^6.0.1",
88
- "typescript": "5.9.3",
89
- "vite": "^8.0.3"
88
+ "typescript": "^6.0.2",
89
+ "vite": "^8.0.8"
90
90
  }
91
91
  }
92
92
 
@@ -597,22 +597,22 @@ export default nextConfig;
597
597
  "@assistant-ui/ui": "workspace:*",
598
598
  "class-variance-authority": "^0.7.1",
599
599
  "clsx": "^2.1.1",
600
- "lucide-react": "^1.7.0",
601
- "next": "^16.2.2",
602
- "react": "^19.2.4",
603
- "react-dom": "^19.2.4",
600
+ "lucide-react": "^1.8.0",
601
+ "next": "^16.2.3",
602
+ "react": "^19.2.5",
603
+ "react-dom": "^19.2.5",
604
604
  "tailwind-merge": "^3.5.0"
605
605
  },
606
606
  "devDependencies": {
607
607
  "@assistant-ui/x-buildutils": "workspace:*",
608
608
  "@tailwindcss/postcss": "^4.2.2",
609
- "@types/node": "^25.5.0",
609
+ "@types/node": "^25.6.0",
610
610
  "@types/react": "^19.2.14",
611
611
  "@types/react-dom": "^19.2.3",
612
- "postcss": "^8.5.8",
612
+ "postcss": "^8.5.9",
613
613
  "tailwindcss": "^4.2.2",
614
614
  "tw-animate-css": "^1.4.0",
615
- "typescript": "5.9.3"
615
+ "typescript": "^6.0.2"
616
616
  }
617
617
  }
618
618
 
@@ -0,0 +1,406 @@
1
+ ---
2
+ title: Mentions
3
+ description: Let users @-mention tools or custom items in the composer to guide the LLM.
4
+ ---
5
+
6
+ Mentions let users type `@` in the composer to open a popover picker, select an item (e.g. a tool), and insert a directive into the message text. The LLM can then use the directive as a hint.
7
+
8
+ ## How It Works
9
+
10
+ ```
11
+ User types "@" → Trigger detected → Adapter provides categories/items
12
+ ↓
13
+ Directive inserted ← User selects item from popover
14
+ ↓
15
+ Message sent with ":tool[Label]{name=id}" in text
16
+ ```
17
+
18
+ The mention system has three layers:
19
+
20
+ 1. **Trigger detection** — watches the composer text for a trigger character (`@` by default) and extracts the query
21
+ 2. **Adapter** — provides the categories and items to display in the popover (e.g. registered tools)
22
+ 3. **Formatter** — serializes a selected item into directive text (`:type[label]{name=id}`) and parses it back for rendering
23
+
24
+ ## Quick Start
25
+
26
+ The fastest path is the pre-built [Mention UI component](/docs/ui/mention), which wires everything together with a single shadcn component:
27
+
28
+ ```bash
29
+ npx shadcn@latest add "https://r.assistant-ui.com/composer-mention"
30
+ ```
31
+
32
+ See the [Mention UI guide](/docs/ui/mention) for setup steps.
33
+
34
+ The rest of this guide covers the underlying concepts and customization points.
35
+
36
+ ## Mention Adapter
37
+
38
+ A `Unstable_MentionAdapter` provides the data for the popover. All methods are **synchronous** — use external state management (React Query, SWR, local state) for async data, then expose loaded results through the adapter.
39
+
40
+ ```ts
41
+ import type { Unstable_MentionAdapter } from "@assistant-ui/core";
42
+
43
+ const myAdapter: Unstable_MentionAdapter = {
44
+ categories() {
45
+ return [
46
+ { id: "tools", label: "Tools" },
47
+ { id: "users", label: "Users" },
48
+ ];
49
+ },
50
+
51
+ categoryItems(categoryId) {
52
+ if (categoryId === "tools") {
53
+ return [
54
+ { id: "search", type: "tool", label: "Search" },
55
+ { id: "calculator", type: "tool", label: "Calculator" },
56
+ ];
57
+ }
58
+ if (categoryId === "users") {
59
+ return [
60
+ { id: "alice", type: "user", label: "Alice" },
61
+ { id: "bob", type: "user", label: "Bob" },
62
+ ];
63
+ }
64
+ return [];
65
+ },
66
+
67
+ // Optional — global search across all categories
68
+ search(query) {
69
+ const lower = query.toLowerCase();
70
+ const all = [
71
+ ...this.categoryItems("tools"),
72
+ ...this.categoryItems("users"),
73
+ ];
74
+ return all.filter(
75
+ (item) =>
76
+ item.label.toLowerCase().includes(lower) ||
77
+ item.id.toLowerCase().includes(lower),
78
+ );
79
+ },
80
+ };
81
+ ```
82
+
83
+ Pass the adapter to `MentionRoot`:
84
+
85
+ ```tsx
86
+ <ComposerPrimitive.Unstable_MentionRoot adapter={myAdapter}>
87
+ <ComposerPrimitive.Root>
88
+ <ComposerPrimitive.Input placeholder="Type @ to mention..." />
89
+ </ComposerPrimitive.Root>
90
+ </ComposerPrimitive.Unstable_MentionRoot>
91
+ ```
92
+
93
+ ### Built-in Tool Adapter
94
+
95
+ For the common case of mentioning registered tools, use `unstable_useToolMentionAdapter`:
96
+
97
+ ```tsx
98
+ import { unstable_useToolMentionAdapter } from "@assistant-ui/react";
99
+
100
+ const adapter = unstable_useToolMentionAdapter({
101
+ // Format tool names for display (default: raw name)
102
+ formatLabel: (name) =>
103
+ name.replaceAll("_", " ").replace(/\b\w/g, (c) => c.toUpperCase()),
104
+
105
+ // Custom category label (default: "Tools")
106
+ categoryLabel: "Tools",
107
+
108
+ // Explicit tool list (overrides model context tools)
109
+ // tools: [{ id: "search", type: "tool", label: "Search" }],
110
+
111
+ // Include model context tools alongside explicit tools
112
+ // includeModelContextTools: true,
113
+ });
114
+ ```
115
+
116
+ The adapter automatically reads tools from the model context (registered via `Tools()` or `useAssistantTool`). When `tools` is provided, model context tools are excluded unless `includeModelContextTools` is set to `true`.
117
+
118
+ ## Directive Format
119
+
120
+ When a user selects a mention item, it is serialized into the composer text as a **directive**. The default format is:
121
+
122
+ ```
123
+ :type[label]{name=id}
124
+ ```
125
+
126
+ For example, selecting a tool named "get_weather" with label "Get Weather" produces:
127
+
128
+ ```
129
+ :tool[Get Weather]{name=get_weather}
130
+ ```
131
+
132
+ When `id` equals `label`, the `{name=…}` attribute is omitted for brevity:
133
+
134
+ ```
135
+ :tool[search]
136
+ ```
137
+
138
+ ### Custom Formatter
139
+
140
+ Implement `Unstable_DirectiveFormatter` to use a different format:
141
+
142
+ ```ts
143
+ import type { Unstable_DirectiveFormatter } from "@assistant-ui/core";
144
+
145
+ const slashFormatter: Unstable_DirectiveFormatter = {
146
+ serialize(item) {
147
+ return `/${item.id}`;
148
+ },
149
+
150
+ parse(text) {
151
+ const segments = [];
152
+ const re = /\/(\w+)/g;
153
+ let lastIndex = 0;
154
+ let match;
155
+
156
+ while ((match = re.exec(text)) !== null) {
157
+ if (match.index > lastIndex) {
158
+ segments.push({ kind: "text" as const, text: text.slice(lastIndex, match.index) });
159
+ }
160
+ segments.push({
161
+ kind: "mention" as const,
162
+ type: "tool",
163
+ label: match[1]!,
164
+ id: match[1]!,
165
+ });
166
+ lastIndex = re.lastIndex;
167
+ }
168
+
169
+ if (lastIndex < text.length) {
170
+ segments.push({ kind: "text" as const, text: text.slice(lastIndex) });
171
+ }
172
+
173
+ return segments;
174
+ },
175
+ };
176
+ ```
177
+
178
+ Pass it to both the mention root and the message renderer:
179
+
180
+ ```tsx
181
+ // Composer
182
+ <ComposerPrimitive.Unstable_MentionRoot adapter={adapter} formatter={slashFormatter}>
183
+ ...
184
+ </ComposerPrimitive.Unstable_MentionRoot>
185
+
186
+ // User messages
187
+ const SlashDirectiveText = createDirectiveText(slashFormatter);
188
+ <MessagePrimitive.Parts components={{ Text: SlashDirectiveText }} />
189
+ ```
190
+
191
+ ## Textarea vs Lexical
192
+
193
+ The mention system supports two input modes:
194
+
195
+ | | Textarea (default) | Lexical |
196
+ | --- | --- | --- |
197
+ | **Input component** | `ComposerPrimitive.Input` | `LexicalComposerInput` |
198
+ | **Mention display in composer** | Raw directive text (`:tool[Label]`) | Inline chips (atomic nodes) |
199
+ | **Dependencies** | None | `@assistant-ui/react-lexical`, `lexical`, `@lexical/react` |
200
+ | **Best for** | Simple setups, minimal bundle | Rich editing, polished UX |
201
+
202
+ With **textarea**, selecting a mention inserts the directive string directly into the text. The user sees `:tool[Get Weather]{name=get_weather}` in the input.
203
+
204
+ With **Lexical**, selected mentions appear as styled inline chips that behave as atomic units — they can be selected, deleted, and undone as a whole. The underlying text still uses the directive format.
205
+
206
+ ```tsx
207
+ import { LexicalComposerInput } from "@assistant-ui/react-lexical";
208
+
209
+ <ComposerPrimitive.Unstable_MentionRoot adapter={adapter}>
210
+ <ComposerPrimitive.Root>
211
+ <LexicalComposerInput placeholder="Type @ to mention..." />
212
+ <ComposerPrimitive.Send />
213
+ </ComposerPrimitive.Root>
214
+ </ComposerPrimitive.Unstable_MentionRoot>
215
+ ```
216
+
217
+ `LexicalComposerInput` auto-wires to the mention context — no extra props needed.
218
+
219
+ ## Rendering Mentions in Messages
220
+
221
+ Use `DirectiveText` as the `Text` component for user messages so directives render as inline chips instead of raw syntax:
222
+
223
+ ```tsx
224
+ import { DirectiveText } from "@/components/assistant-ui/composer-mention";
225
+
226
+ <MessagePrimitive.Parts
227
+ components={{
228
+ Text: DirectiveText,
229
+ }}
230
+ />
231
+ ```
232
+
233
+ For assistant messages, keep using your markdown renderer (e.g. `MarkdownText`) — the LLM typically does not emit directive syntax.
234
+
235
+ For a custom formatter, use `createDirectiveText`:
236
+
237
+ ```tsx
238
+ import { createDirectiveText } from "@/components/assistant-ui/composer-mention";
239
+
240
+ const MyDirectiveText = createDirectiveText(myFormatter);
241
+ ```
242
+
243
+ ## Processing Mentions on the Backend
244
+
245
+ The message text arrives at your backend with directives inline. Parse them to extract mentioned items:
246
+
247
+ ```ts
248
+ // Default format: :type[label]{name=id}
249
+ const DIRECTIVE_RE = /:([\w-]+)\[([^\]]+)\](?:\{name=([^}]+)\})?/g;
250
+
251
+ function parseMentions(text: string) {
252
+ const mentions = [];
253
+ let match;
254
+ while ((match = DIRECTIVE_RE.exec(text)) !== null) {
255
+ mentions.push({
256
+ type: match[1], // e.g. "tool"
257
+ label: match[2], // e.g. "Get Weather"
258
+ id: match[3] ?? match[2], // e.g. "get_weather"
259
+ });
260
+ }
261
+ return mentions;
262
+ }
263
+
264
+ // Example:
265
+ // parseMentions("Use :tool[Get Weather]{name=get_weather} to check")
266
+ // → [{ type: "tool", label: "Get Weather", id: "get_weather" }]
267
+ ```
268
+
269
+ You can use the extracted mentions to:
270
+ - Force-enable specific tools for the LLM call
271
+ - Add context about mentioned users or documents to the system prompt
272
+ - Log which tools users request most often
273
+
274
+ ## Reading Mention State
275
+
276
+ Use `unstable_useMentionContext` to programmatically access the mention popover state:
277
+
278
+ ```tsx
279
+ import { unstable_useMentionContext } from "@assistant-ui/react";
280
+
281
+ function MyComponent() {
282
+ const mention = unstable_useMentionContext();
283
+
284
+ // mention.open — whether the popover is visible
285
+ // mention.query — current search text after "@"
286
+ // mention.categories — filtered category list
287
+ // mention.items — filtered item list
288
+ // mention.highlightedIndex — keyboard-navigated index
289
+ // mention.isSearchMode — true when global search is active
290
+ // mention.selectItem(item) — programmatically select an item
291
+ // mention.close() — close the popover
292
+ }
293
+ ```
294
+
295
+ This hook must be used within a `ComposerPrimitive.Unstable_MentionRoot`.
296
+
297
+ ## Building a Custom Popover
298
+
299
+ Use the mention primitives to build a fully custom popover:
300
+
301
+ ```tsx
302
+ <ComposerPrimitive.Unstable_MentionRoot adapter={adapter}>
303
+ <ComposerPrimitive.Root>
304
+ <ComposerPrimitive.Input />
305
+
306
+ <ComposerPrimitive.Unstable_MentionPopover className="popover">
307
+ <ComposerPrimitive.Unstable_MentionBack>
308
+ ← Back
309
+ </ComposerPrimitive.Unstable_MentionBack>
310
+
311
+ <ComposerPrimitive.Unstable_MentionCategories>
312
+ {(categories) =>
313
+ categories.map((cat) => (
314
+ <ComposerPrimitive.Unstable_MentionCategoryItem
315
+ key={cat.id}
316
+ categoryId={cat.id}
317
+ >
318
+ {cat.label}
319
+ </ComposerPrimitive.Unstable_MentionCategoryItem>
320
+ ))
321
+ }
322
+ </ComposerPrimitive.Unstable_MentionCategories>
323
+
324
+ <ComposerPrimitive.Unstable_MentionItems>
325
+ {(items) =>
326
+ items.map((item) => (
327
+ <ComposerPrimitive.Unstable_MentionItem
328
+ key={item.id}
329
+ item={item}
330
+ >
331
+ {item.label}
332
+ </ComposerPrimitive.Unstable_MentionItem>
333
+ ))
334
+ }
335
+ </ComposerPrimitive.Unstable_MentionItems>
336
+ </ComposerPrimitive.Unstable_MentionPopover>
337
+
338
+ <ComposerPrimitive.Send />
339
+ </ComposerPrimitive.Root>
340
+ </ComposerPrimitive.Unstable_MentionRoot>
341
+ ```
342
+
343
+ ### Primitives Reference
344
+
345
+ | Primitive | Description |
346
+ | --- | --- |
347
+ | `Unstable_MentionRoot` | Provider — wraps the composer with trigger detection, keyboard navigation, and popover state |
348
+ | `Unstable_MentionPopover` | Container — only renders when a trigger is active (`role="listbox"`) |
349
+ | `Unstable_MentionCategories` | Render-function for the top-level category list |
350
+ | `Unstable_MentionCategoryItem` | Button that drills into a category (`role="option"`, auto `data-highlighted`) |
351
+ | `Unstable_MentionItems` | Render-function for items within the active category or search results |
352
+ | `Unstable_MentionItem` | Button that inserts a mention (`role="option"`, auto `data-highlighted`) |
353
+ | `Unstable_MentionBack` | Button that navigates back from items to categories |
354
+
355
+ See the [Composer API reference](/docs/api-reference/primitives/composer) for full prop details.
356
+
357
+ ## Combining with Slash Commands
358
+
359
+ Mentions and [slash commands](/docs/guides/slash-commands) can coexist on the same composer. Both are built on the same [trigger popover architecture](/docs/guides/slash-commands#trigger-popover-architecture) — nest both roots and they work independently:
360
+
361
+ ```tsx
362
+ <ComposerPrimitive.Unstable_MentionRoot adapter={mentionAdapter}>
363
+ <ComposerPrimitive.Unstable_SlashCommandRoot adapter={slashAdapter}>
364
+ <ComposerPrimitive.Root>
365
+ <ComposerPrimitive.Input placeholder="Type @ to mention, / for commands..." />
366
+ <ComposerPrimitive.Send />
367
+
368
+ {/* Mention popover (shows on @) */}
369
+ <ComposerPrimitive.Unstable_MentionPopover>
370
+ <ComposerPrimitive.Unstable_MentionCategories>
371
+ {(categories) => categories.map((cat) => (
372
+ <ComposerPrimitive.Unstable_MentionCategoryItem key={cat.id} categoryId={cat.id}>
373
+ {cat.label}
374
+ </ComposerPrimitive.Unstable_MentionCategoryItem>
375
+ ))}
376
+ </ComposerPrimitive.Unstable_MentionCategories>
377
+ <ComposerPrimitive.Unstable_MentionItems>
378
+ {(items) => items.map((item) => (
379
+ <ComposerPrimitive.Unstable_MentionItem key={item.id} item={item}>
380
+ {item.label}
381
+ </ComposerPrimitive.Unstable_MentionItem>
382
+ ))}
383
+ </ComposerPrimitive.Unstable_MentionItems>
384
+ </ComposerPrimitive.Unstable_MentionPopover>
385
+
386
+ {/* Slash command popover (shows on /) */}
387
+ <ComposerPrimitive.Unstable_TriggerPopoverPopover>
388
+ <ComposerPrimitive.Unstable_TriggerPopoverItems>
389
+ {(items) => items.map((item, index) => (
390
+ <ComposerPrimitive.Unstable_TriggerPopoverItem key={item.id} item={item} index={index}>
391
+ {item.label}
392
+ </ComposerPrimitive.Unstable_TriggerPopoverItem>
393
+ ))}
394
+ </ComposerPrimitive.Unstable_TriggerPopoverItems>
395
+ </ComposerPrimitive.Unstable_TriggerPopoverPopover>
396
+ </ComposerPrimitive.Root>
397
+ </ComposerPrimitive.Unstable_SlashCommandRoot>
398
+ </ComposerPrimitive.Unstable_MentionRoot>
399
+ ```
400
+
401
+ ## Related
402
+
403
+ - [Mention UI Component](/docs/ui/mention) — pre-built shadcn component
404
+ - [Slash Commands Guide](/docs/guides/slash-commands) — `/` command system built on the same architecture
405
+ - [Tools Guide](/docs/guides/tools) — register tools that appear in the mention picker
406
+ - [Composer Primitives](/docs/primitives/composer) — underlying composer primitives