Dice UI
Components

Mention

A component that allows to mention items in a list by a trigger character.

"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.

PropTypeDefault
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 AttributeValue
[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.

PropTypeDefault
asChild?
boolean
-
onResizeCapture?
ReactEventHandler<HTMLLabelElement>
-
onResize?
ReactEventHandler<HTMLLabelElement>
-

Input

The text input field that users can type into to trigger mentions.

PropTypeDefault
asChild?
boolean
-
onResizeCapture?
ReactEventHandler<HTMLInputElement>
-
onResize?
ReactEventHandler<HTMLInputElement>
-

Portal

A portal for rendering the mention content outside of its DOM hierarchy.

PropTypeDefault
container?
Element | DocumentFragment | null
document.body

Content

The popover container for mention items. Positions the mention popover relative to the cursor position.

PropTypeDefault
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 AttributeValue
[data-state]"open" | "closed"
[data-side]"top" | "right" | "bottom" | "left"
[data-align]"start" | "center" | "end"
CSS VariableDescription
--dice-transform-originTransform origin for cursor positioning.
--dice-available-widthAvailable width in the viewport for the popover element.
--dice-available-heightAvailable height in the viewport for the popover element.

Item

An interactive option in the mention list.

PropTypeDefault
disabled?
boolean
-
value
string
-
asChild?
boolean
-
onResizeCapture?
ReactEventHandler<HTMLDivElement>
-
onResize?
ReactEventHandler<HTMLDivElement>
-
label?
string
-
Data AttributeValue
[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

KeyDescription
EnterWhen open, selects the highlighted mention option.
ArrowUpWhen open, highlights the previous mention option.
ArrowDownWhen open, highlights the next mention option.
HomeWhen open, highlights the first mention option.
EndWhen open, highlights the last mention option.
EscapeCloses the mention popover and returns focus to the input.