Chuyển tới nội dung
Component

ContextMenu

A menu that opens at the pointer on right click, on the keyboard context menu keys, or on a touch long press over an area, with the same items, groups, checkbox and radio items, shortcuts and destructive style as DropdownMenu. Every action in it must also be reachable another way.

Xem trước

Props

  • disabled
  • longPressDelay
  • loop
  • avoidCollisions
  • items
import { IconCopy, IconPencil, IconTrash } from "@tabler/icons-react"
import { Button } from "@/components/ui/button"
import { ContextMenu, ContextMenuContent, ContextMenuItem, ContextMenuSeparator, ContextMenuTrigger } from "@/components/ui/context-menu"

<div className="flex w-64 max-w-full flex-col gap-md">
  <div role="group" aria-label="File actions" className="flex gap-sm">
    <Button size="sm" variant="outline">Rename</Button>
    <Button size="sm" variant="outline">Delete</Button>
  </div>
  <ContextMenu>
    <ContextMenuTrigger className="flex min-h-28 flex-col justify-center gap-sm rounded-container border border-dashed border-border px-lg py-xl" aria-label="More actions for report.pdf">
      <p className="text-sm font-medium text-text-primary">report.pdf</p>
      <p className="text-xs text-text-secondary">Right click, long press or Shift+F10</p>
    </ContextMenuTrigger>
    <ContextMenuContent aria-label="File actions">
      <ContextMenuItem leadingIcon={<IconPencil size={16} />} shortcut="F2">Rename</ContextMenuItem>
      <ContextMenuItem disabled leadingIcon={<IconCopy size={16} />}>Duplicate</ContextMenuItem>
      <ContextMenuSeparator />
      <ContextMenuItem destructive leadingIcon={<IconTrash size={16} />}>Delete</ContextMenuItem>
    </ContextMenuContent>
  </ContextMenu>
</div>

Cách dùng

Khi nào dùng

  • Offer the same actions as a visible menu when a user right-clicks, uses a menu key or long-presses an item.

Khi nào không dùng

  • The only way to reach an action. Keep the same actions available elsewhere.
  • An action menu on touch without a long-press affordance. Use DropdownMenu.

Ví dụ

File row

import { IconPencil, IconTrash } from "@tabler/icons-react"
import { Button } from "@/components/ui/button"
import { ContextMenu, ContextMenuContent, ContextMenuItem, ContextMenuSeparator, ContextMenuTrigger } from "@/components/ui/context-menu"

<div className="flex w-64 max-w-full flex-col gap-md">
  <div role="group" aria-label="File actions" className="flex gap-sm">
    <Button size="sm" variant="outline">Rename</Button>
    <Button size="sm" variant="outline">Delete</Button>
  </div>
  <ContextMenu>
    <ContextMenuTrigger className="rounded-container border border-dashed border-border px-lg py-xl" aria-label="More actions for report.pdf">
      <p className="text-sm font-medium">report.pdf</p>
      <p className="text-xs text-text-secondary">Right click, long press or Shift+F10</p>
    </ContextMenuTrigger>
    <ContextMenuContent aria-label="File actions">
      <ContextMenuItem leadingIcon={<IconPencil size={16} />} shortcut="F2">Rename</ContextMenuItem>
      <ContextMenuItem disabled>Duplicate</ContextMenuItem>
      <ContextMenuSeparator />
      <ContextMenuItem destructive leadingIcon={<IconTrash size={16} />}>Delete</ContextMenuItem>
    </ContextMenuContent>
  </ContextMenu>
</div>

Checkbox options

import { useState } from "react"
import { IconPencil, IconTrash } from "@tabler/icons-react"
import { Button } from "@/components/ui/button"
import { ContextMenu, ContextMenuCheckboxItem, ContextMenuContent, ContextMenuItem, ContextMenuLabel, ContextMenuSeparator, ContextMenuTrigger } from "@/components/ui/context-menu"

const [menuPinned, setMenuPinned] = useState(false)

<div className="flex w-64 max-w-full flex-col gap-md">
  <div role="group" aria-label="File actions" className="flex gap-sm">
    <Button size="sm" variant="outline">Rename</Button>
    <Button size="sm" variant="outline">Delete</Button>
  </div>
  <ContextMenu>
    <ContextMenuTrigger className="rounded-container border border-dashed border-border px-lg py-xl" aria-label="More actions for report.pdf">
      <p className="text-sm font-medium">report.pdf</p>
      <p className="text-xs text-text-secondary">Right click, long press or Shift+F10</p>
    </ContextMenuTrigger>
    <ContextMenuContent aria-label="File actions">
      <ContextMenuLabel>View</ContextMenuLabel>
      <ContextMenuCheckboxItem checked={menuPinned} onCheckedChange={setMenuPinned}>Pin to top</ContextMenuCheckboxItem>
      <ContextMenuSeparator />
      <ContextMenuItem leadingIcon={<IconPencil size={16} />} shortcut="F2">Rename</ContextMenuItem>
      <ContextMenuItem disabled>Duplicate</ContextMenuItem>
      <ContextMenuSeparator />
      <ContextMenuItem destructive leadingIcon={<IconTrash size={16} />}>Delete</ContextMenuItem>
    </ContextMenuContent>
  </ContextMenu>
</div>

Radio options

import { useState } from "react"
import { IconPencil, IconTrash } from "@tabler/icons-react"
import { Button } from "@/components/ui/button"
import { ContextMenu, ContextMenuContent, ContextMenuItem, ContextMenuLabel, ContextMenuRadioGroup, ContextMenuRadioItem, ContextMenuSeparator, ContextMenuTrigger } from "@/components/ui/context-menu"

const [menuSort, setMenuSort] = useState("name")

<div className="flex w-64 max-w-full flex-col gap-md">
  <div role="group" aria-label="File actions" className="flex gap-sm">
    <Button size="sm" variant="outline">Rename</Button>
    <Button size="sm" variant="outline">Delete</Button>
  </div>
  <ContextMenu>
    <ContextMenuTrigger className="rounded-container border border-dashed border-border px-lg py-xl" aria-label="More actions for report.pdf">
      <p className="text-sm font-medium">report.pdf</p>
      <p className="text-xs text-text-secondary">Right click, long press or Shift+F10</p>
    </ContextMenuTrigger>
    <ContextMenuContent aria-label="File actions">
      <ContextMenuLabel>Sort by</ContextMenuLabel>
      <ContextMenuRadioGroup value={menuSort} onValueChange={setMenuSort}>
        <ContextMenuRadioItem value="name">Name</ContextMenuRadioItem>
        <ContextMenuRadioItem value="date">Date</ContextMenuRadioItem>
      </ContextMenuRadioGroup>
      <ContextMenuSeparator />
      <ContextMenuItem leadingIcon={<IconPencil size={16} />} shortcut="F2">Rename</ContextMenuItem>
      <ContextMenuItem disabled>Duplicate</ContextMenuItem>
      <ContextMenuSeparator />
      <ContextMenuItem destructive leadingIcon={<IconTrash size={16} />}>Delete</ContextMenuItem>
    </ContextMenuContent>
  </ContextMenu>
</div>

Disabled area

report.pdf

Right click, long press or Shift+F10

import { IconPencil, IconTrash } from "@tabler/icons-react"
import { Button } from "@/components/ui/button"
import { ContextMenu, ContextMenuContent, ContextMenuItem, ContextMenuSeparator, ContextMenuTrigger } from "@/components/ui/context-menu"

<div className="flex w-64 max-w-full flex-col gap-md">
  <div role="group" aria-label="File actions" className="flex gap-sm">
    <Button size="sm" variant="outline">Rename</Button>
    <Button size="sm" variant="outline">Delete</Button>
  </div>
  <ContextMenu>
    <ContextMenuTrigger disabled className="rounded-container border border-dashed border-border px-lg py-xl" aria-label="More actions for report.pdf">
      <p className="text-sm font-medium">report.pdf</p>
      <p className="text-xs text-text-secondary">Right click, long press or Shift+F10</p>
    </ContextMenuTrigger>
    <ContextMenuContent aria-label="File actions">
      <ContextMenuItem leadingIcon={<IconPencil size={16} />} shortcut="F2">Rename</ContextMenuItem>
      <ContextMenuItem disabled>Duplicate</ContextMenuItem>
      <ContextMenuSeparator />
      <ContextMenuItem destructive leadingIcon={<IconTrash size={16} />}>Delete</ContextMenuItem>
    </ContextMenuContent>
  </ContextMenu>
</div>

Nên và không nên

Nên

  • Mirror a visible menu or toolbar; the context menu is a shortcut.
  • Use the same item names as the visible menu.
  • Provide an accessible name on ContextMenuContent when the trigger label alone does not explain the actions. ContextMenuTrigger adds tabIndex 0 unless disabled.

Không nên

  • Do not make it the only way to an action.
  • Do not override the browser menu on text inputs or links.

Props

TênKiểuGiá trịMặc địnhMô tả
onOpenChange(open: boolean) => void--Called when it opens or closes.
modalboolean-trueBlock outside interaction while open.
disabledboolean-falseOn ContextMenuTrigger: bypasses the custom menu and preserves the browser menu. On item parts: prevents selection.
asChildboolean-falseOn ContextMenuTrigger or ContextMenuItem: use the only child as the rendered element. Interactive links and text fields keep their native context menu.
longPressDelaynumber-500Milliseconds a touch must be held before the menu opens.
loopboolean-falseOn ContextMenuContent: wrap arrow-key navigation from the last enabled item to the first.
avoidCollisionsboolean-trueOn ContextMenuContent: shift or flip the menu to keep it inside the viewport.
collisionPaddingnumber | Partial<Record<"top" | "right" | "bottom" | "left", number>>-8On ContextMenuContent: minimum distance from viewport edges in px.
onSelect(event: Event) => void--On ContextMenuItem: runs when selected by pointer or keyboard. Call event.preventDefault() to keep the menu open.
destructiveboolean-falseOn ContextMenuItem: danger text and icon for a destructive action.
insetboolean-falseOn ContextMenuItem and ContextMenuLabel: align the text with rows that contain icons or selection indicators.
leadingIconnode--On ContextMenuItem: decorative 16px icon. Ignored with asChild.
shortcutstring--On ContextMenuItem: a displayed key hint; the application handles the shortcut.
checkedboolean | "indeterminate"--On ContextMenuCheckboxItem: controlled check state.
onCheckedChange(checked: boolean) => void--On ContextMenuCheckboxItem: receives the next check state.
valuestring--On ContextMenuRadioGroup: the selected value. On ContextMenuRadioItem: the required option identifier.
onValueChange(value: string) => void--On ContextMenuRadioGroup: receives the selected radio item value.

Slot

TênBắt buộcMô tả
childrencóContextMenuTrigger and ContextMenuContent with item parts.

Cấu tạo

PhầnMô tả
rootContextMenu: holds open state.
triggerContextMenuTrigger: the area that listens for right click.
contentContextMenuContent: the same floating list as DropdownMenuContent, positioned at the pointer or trigger.
itemContextMenuItem: same as DropdownMenuItem.
iconThe leading icon of an item, as in DropdownMenu.
destructiveA destructive ContextMenuItem, as in DropdownMenu.
destructiveIconThe leading icon of a destructive item, in the danger text color.
indicatorThe check or radio dot of a checkbox or radio item, as in DropdownMenu.
groupLabelContextMenuLabel: a group heading, as in DropdownMenu.
separatorContextMenuSeparator: a line between groups, as in DropdownMenu.
shortcutContextMenuShortcut: the shortcut hint, as in DropdownMenu.

Khả năng truy cập

Role: menu

PhímHành động
Shift+F10 / ContextMenu keyOpens the menu for the focused trigger area.
ArrowDown / ArrowUpMoves between items.
Enter / SpaceActivates the item.
EscapeCloses the menu and returns focus to the trigger area.
Type a letterMoves to the next item starting with it.
  • Items are the DropdownMenu item parts (shared styles and keyboard), exported under ContextMenu names.
  • The trigger area must be focusable (tabIndex 0 with an accessible name) for keyboard users.
  • The menu is positioned at the pointer, or at the trigger's start when opened from the keyboard.
  • Touch: a long press (about 500ms without moving more than a few px) opens the menu at the touch point. The component implements it itself because iOS Safari does not fire contextmenu on long press. If a native contextmenu event follows the long press, it is ignored so the menu does not open twice.
  • Mouse and keyboard stay for devices with them attached: right click, Shift+F10 and the Menu key.
  • The trigger area sets -webkit-touch-callout: none and user-select: none so the browser callout and text selection do not fight the long press.
  • Submenu parts are not exported. Use groups and separators for sections within this menu.

Cài đặt

npx werewolf-ui add context-menu

Chạy npx werewolf-ui init một lần trong app trước khi thêm component. Init cài token, thêm CSS import và luật cho agent. Add cài component này cùng các dependency và giữ nguyên theme của bạn. Xem Cài đặt để biết yêu cầu và cách cài qua registry.