Components
"use client";
import * as Mention from "@diceui/mention";
import * as React from "react";
const users = [
{
id: "1",
name: "Olivia Martin",
email: "[email protected]",
},
{
id: "2",
name: "Isabella Nguyen",
email: "[email protected]",
},
{
id: "3",
name: "Emma Wilson",
email: "[email protected]",
},
{
id: "4",
name: "Jackson Lee",
email: "[email protected]",
},
{
id: "5",
name: "William Kim",
email: "[email protected]",
},
];
export function MentionDemo() {
return (
<Mention.Root className="w-full max-w-[400px] **:data-tag:rounded **:data-tag:bg-blue-200 **:data-tag:py-px **:data-tag:text-blue-950 dark:**:data-tag:bg-blue-800 dark:**:data-tag:text-blue-50">
<Mention.Label className="font-medium text-sm text-zinc-950 leading-none peer-disabled:cursor-not-allowed peer-disabled:opacity-70 dark:text-zinc-50">
Mention users
</Mention.Label>
<Mention.Input
placeholder="Type @ to mention someone..."
className="flex min-h-[60px] w-full rounded-md border border-zinc-200 bg-transparent px-3 py-2 text-base shadow-xs placeholder:text-muted-foreground focus-visible:outline-hidden focus-visible:ring-1 focus-visible:ring-zinc-800 disabled:cursor-not-allowed disabled:opacity-50 md:text-sm dark:border-zinc-800 dark:focus-visible:ring-zinc-300"
asChild
>
<textarea />
</Mention.Input>
<Mention.Portal>
<Mention.Content className="data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2 relative z-50 min-w-[var(--dice-anchor-width)] overflow-hidden rounded-md border border-zinc-200 bg-white p-1 text-zinc-950 shadow-md data-[state=closed]:animate-out data-[state=open]:animate-in dark:border-zinc-800 dark:bg-zinc-950 dark:text-zinc-50">
{users.map((user) => (
<Mention.Item
key={user.id}
value={user.name}
className="relative flex w-full cursor-default select-none flex-col rounded-sm px-2 py-1.5 text-sm outline-hidden data-disabled:pointer-events-none data-highlighted:bg-zinc-100 data-highlighted:text-zinc-900 data-disabled:opacity-50 dark:data-highlighted:bg-zinc-800 dark:data-highlighted:text-zinc-50"
>
<span className="text-sm">{user.name}</span>
<span className="text-muted-foreground text-xs">
{user.email}
</span>
</Mention.Item>
))}
</Mention.Content>
</Mention.Portal>
</Mention.Root>
);
}
Installation
npm install @diceui/mention
pnpm add @diceui/mention
yarn add @diceui/mention
bun add @diceui/mention
Installation with shadcn/ui
CLI
npx shadcn@latest add "https://diceui.com/r/mention"
pnpm dlx shadcn@latest add "https://diceui.com/r/mention"
yarn dlx shadcn@latest add "https://diceui.com/r/mention"
bun x shadcn@latest add "https://diceui.com/r/mention"
Manual
Install the following dependencies:
npm install @diceui/mention
pnpm add @diceui/mention
yarn add @diceui/mention
bun add @diceui/mention
Copy and paste the following code into your project.
import * as MentionPrimitive from "@diceui/mention";
import * as React from "react";
import { cn } from "@/lib/utils";
const Mention = React.forwardRef<
React.ComponentRef<typeof MentionPrimitive.Root>,
React.ComponentPropsWithoutRef<typeof MentionPrimitive.Root>
>(({ className, ...props }, ref) => (
<MentionPrimitive.Root
data-slot="mention"
ref={ref}
className={cn(
"**:data-tag:rounded **:data-tag:bg-blue-200 **:data-tag:py-px **:data-tag:text-blue-950 dark:**:data-tag:bg-blue-800 dark:**:data-tag:text-blue-50",
className,
)}
{...props}
/>
));
Mention.displayName = MentionPrimitive.Root.displayName;
const MentionLabel = React.forwardRef<
React.ComponentRef<typeof MentionPrimitive.Label>,
React.ComponentPropsWithoutRef<typeof MentionPrimitive.Label>
>(({ className, ...props }, ref) => (
<MentionPrimitive.Label
data-slot="mention-label"
ref={ref}
className={cn("px-0.5 py-1.5 font-semibold text-sm", className)}
{...props}
/>
));
MentionLabel.displayName = MentionPrimitive.Label.displayName;
const MentionInput = React.forwardRef<
React.ComponentRef<typeof MentionPrimitive.Input>,
React.ComponentPropsWithoutRef<typeof MentionPrimitive.Input>
>(({ className, ...props }, ref) => (
<MentionPrimitive.Input
data-slot="mention-input"
ref={ref}
className={cn(
"flex w-full rounded-md border border-input bg-transparent px-3 py-2 text-sm shadow-xs placeholder:text-muted-foreground focus-visible:outline-hidden focus-visible:ring-1 focus-visible:ring-ring disabled:cursor-not-allowed disabled:opacity-50",
className,
)}
{...props}
/>
));
MentionInput.displayName = MentionPrimitive.Input.displayName;
const MentionContent = React.forwardRef<
React.ComponentRef<typeof MentionPrimitive.Content>,
React.ComponentPropsWithoutRef<typeof MentionPrimitive.Content>
>(({ className, children, ...props }, ref) => (
<MentionPrimitive.Portal>
<MentionPrimitive.Content
data-slot="mention-content"
ref={ref}
className={cn(
"data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2 relative z-50 min-w-[8rem] overflow-hidden rounded-md border bg-popover p-1 text-popover-foreground shadow-md data-[state=closed]:animate-out data-[state=open]:animate-in",
className,
)}
{...props}
>
{children}
</MentionPrimitive.Content>
</MentionPrimitive.Portal>
));
MentionContent.displayName = MentionPrimitive.Content.displayName;
const MentionItem = React.forwardRef<
React.ComponentRef<typeof MentionPrimitive.Item>,
React.ComponentPropsWithoutRef<typeof MentionPrimitive.Item>
>(({ className, children, ...props }, ref) => (
<MentionPrimitive.Item
data-slot="mention-item"
ref={ref}
className={cn(
"relative flex w-full cursor-default select-none items-center gap-2 rounded-sm px-2 py-1.5 text-sm outline-hidden data-disabled:pointer-events-none data-highlighted:bg-accent data-highlighted:text-accent-foreground data-disabled:opacity-50",
className,
)}
{...props}
>
{children}
</MentionPrimitive.Item>
));
MentionItem.displayName = MentionPrimitive.Item.displayName;
export { Mention, MentionContent, MentionInput, MentionItem, MentionLabel };
Layout
Import the parts, and compose them together.
import * as Mention from "@diceui/mention";
<Mention.Root>
<Mention.Label />
<Mention.Input />
<Mention.Portal>
<Mention.Content>
<Mention.Item />
</Mention.Content>
</Mention.Portal>
</Mention.Root>
Examples
Custom Trigger
"use client";
import { Textarea } from "@/components/ui/textarea";
import {
Mention,
MentionContent,
MentionInput,
MentionItem,
} from "@/components/ui/mention";
const users = [
{
id: "1",
name: "Olivia Martin",
email: "[email protected]",
},
{
id: "2",
name: "Isabella Nguyen",
email: "[email protected]",
},
{
id: "3",
name: "Emma Wilson",
email: "[email protected]",
},
{
id: "4",
name: "Jackson Lee",
email: "[email protected]",
},
{
id: "5",
name: "William Kim",
email: "[email protected]",
},
];
export function MentionCustomTriggerDemo() {
return (
<Mention trigger="#" className="w-full max-w-[400px]">
<MentionInput placeholder="Type # to mention a user..." asChild>
<Textarea />
</MentionInput>
<MentionContent>
{users.map((user) => (
<MentionItem
key={user.id}
value={user.name}
className="flex-col items-start gap-0.5"
>
<span className="text-sm">{user.name}</span>
<span className="text-muted-foreground text-xs">{user.email}</span>
</MentionItem>
))}
</MentionContent>
</Mention>
);
}
With Custom Filter
"use client";
import { Textarea } from "@/components/ui/textarea";
import {
Mention,
MentionContent,
MentionInput,
MentionItem,
} from "@/components/ui/mention";
import * as React from "react";
const commands = [
{
id: "1",
name: "help",
description: "Show available commands",
},
{
id: "2",
name: "clear",
description: "Clear the console",
},
{
id: "3",
name: "restart",
description: "Restart the application",
},
{
id: "4",
name: "reload",
description: "Reload the current page",
},
{
id: "5",
name: "quit",
description: "Exit the application",
},
];
export function MentionCustomFilterDemo() {
const [value, setValue] = React.useState<string[]>([]);
const [inputValue, setInputValue] = React.useState("");
// Custom filter that matches commands starting with the search term
function onFilter(options: string[], term: string) {
return options.filter((option) =>
option.toLowerCase().startsWith(term.toLowerCase()),
);
}
return (
<Mention
value={value}
onValueChange={setValue}
inputValue={inputValue}
onInputValueChange={setInputValue}
trigger="/"
onFilter={onFilter}
className="w-full max-w-[400px]"
>
<MentionInput placeholder="Type / to use a command..." asChild>
<Textarea />
</MentionInput>
<MentionContent>
{commands.map((command) => (
<MentionItem
key={command.id}
label={command.name}
value={command.name}
>
<span className="font-mono text-sm">{command.name}</span>
<span className="text-muted-foreground text-xs">
{command.description}
</span>
</MentionItem>
))}
</MentionContent>
</Mention>
);
}
API Reference
Root
The container for all mention parts. Mention tags can be styled using the data-tag
attribute within the root.
Prop | Type | Default |
---|---|---|
name? | string | - |
required? | boolean | false |
readonly? | boolean | false |
modal? | boolean | false |
loop? | boolean | false |
exactMatch? | boolean | false |
onFilter? | ((options: string[], term: string) => string[]) | - |
disabled? | boolean | - |
trigger? | string | - |
onInputValueChange? | ((value: string) => void) | - |
inputValue? | string | - |
onOpenChange? | ((open: boolean) => void) | - |
defaultOpen? | boolean | - |
open? | boolean | - |
asChild? | boolean | - |
onResizeCapture? | ReactEventHandler<HTMLDivElement> | - |
onResize? | ReactEventHandler<HTMLDivElement> | - |
onValueChange? | ((value: string[]) => void) | - |
value? | string[] | - |
defaultValue? | string[] | - |
Data Attribute | Value |
---|---|
[data-state] | "open" | "closed" |
[data-disabled] | Present when disabled. |
Label
An accessible label that describes the mention input. Associates with the input element for screen readers.
Prop | Type | Default |
---|---|---|
asChild? | boolean | - |
onResizeCapture? | ReactEventHandler<HTMLLabelElement> | - |
onResize? | ReactEventHandler<HTMLLabelElement> | - |
Input
The text input field that users can type into to trigger mentions.
Prop | Type | Default |
---|---|---|
asChild? | boolean | - |
onResizeCapture? | ReactEventHandler<HTMLInputElement> | - |
onResize? | ReactEventHandler<HTMLInputElement> | - |
Portal
A portal for rendering the mention content outside of its DOM hierarchy.
Prop | Type | Default |
---|---|---|
container? | Element | DocumentFragment | null | document.body |
Content
The popover container for mention items. Positions the mention popover relative to the cursor position.
Prop | Type | Default |
---|---|---|
onPointerDownOutside? | ((event: PointerDownOutsideEvent) => void) | - |
onEscapeKeyDown? | ((event: KeyboardEvent) => void) | - |
asChild? | boolean | - |
onResizeCapture? | ReactEventHandler<HTMLDivElement> | - |
onResize? | ReactEventHandler<HTMLDivElement> | - |
trackAnchor? | boolean | true |
hideWhenDetached? | boolean | false |
forceMount? | boolean | false |
fitViewport? | boolean | false |
avoidCollisions? | boolean | true |
strategy? | Strategy | "absolute" |
sticky? | "partial" | "always" | "partial" |
arrowPadding? | number | 0 |
collisionPadding? | number | Partial<Record<Side, number>> | 0 |
collisionBoundary? | Boundary | - |
alignOffset? | number | 0 |
align? | Align | "start" |
sideOffset? | number | 4 |
side? | Side | "bottom" |
Data Attribute | Value |
---|---|
[data-state] | "open" | "closed" |
[data-side] | "top" | "right" | "bottom" | "left" |
[data-align] | "start" | "center" | "end" |
CSS Variable | Description |
---|---|
--dice-transform-origin | Transform origin for cursor positioning. |
--dice-available-width | Available width in the viewport for the popover element. |
--dice-available-height | Available height in the viewport for the popover element. |
Item
An interactive option in the mention list.
Prop | Type | Default |
---|---|---|
disabled? | boolean | - |
value | string | - |
asChild? | boolean | - |
onResizeCapture? | ReactEventHandler<HTMLDivElement> | - |
onResize? | ReactEventHandler<HTMLDivElement> | - |
label? | string | - |
Data Attribute | Value |
---|---|
[data-highlighted] | Present when the item is highlighted. |
[data-disabled] | Present when the item is disabled. |
[data-value] | The value of the item. |
Accessibility
Keyboard Interactions
Key | Description |
---|---|
Enter | When open, selects the highlighted mention option. |
ArrowUp | When open, highlights the previous mention option. |
ArrowDown | When open, highlights the next mention option. |
Home | When open, highlights the first mention option. |
End | When open, highlights the last mention option. |
Escape | Closes the mention popover and returns focus to the input. |