Skip to main content

Overview

Right-click anywhere in this area
Delete
Context Menu opens on right-click (or long-press on mobile) over a target area, providing context-sensitive actions for that element. It shares the same item primitives as DropdownMenu.

Installation

npm install @araf-ds/core

Usage

import {
  ContextMenu,
  ContextMenuTrigger,
  ContextMenuContent,
  ContextMenuItem,
  ContextMenuSeparator,
  ContextMenuShortcut,
} from "@araf-ds/core"

export default function Example() {
  return (
    <ContextMenu>
      <ContextMenuTrigger className="border-2 border-dashed rounded-lg p-8 text-sm text-muted-foreground">
        Right-click here
      </ContextMenuTrigger>
      <ContextMenuContent className="w-48">
        <ContextMenuItem>
          <EditIcon size={13} /> Edit
          <ContextMenuShortcut>⌘E</ContextMenuShortcut>
        </ContextMenuItem>
        <ContextMenuItem>
          <LinkIcon size={13} /> Copy link
          <ContextMenuShortcut>⌘C</ContextMenuShortcut>
        </ContextMenuItem>
        <ContextMenuSeparator />
        <ContextMenuItem variant="destructive">
          <TrashIcon size={13} /> Delete
          <ContextMenuShortcut></ContextMenuShortcut>
        </ContextMenuItem>
      </ContextMenuContent>
    </ContextMenu>
  )
}

Variants

With Checkbox and Radio Items

<ContextMenuContent>
  <ContextMenuCheckboxItem
    checked={showBookmarks}
    onCheckedChange={setShowBookmarks}
  >
    Show bookmarks <ContextMenuShortcut>⌘⇧B</ContextMenuShortcut>
  </ContextMenuCheckboxItem>
  <ContextMenuSeparator />
  <ContextMenuRadioGroup value={person} onValueChange={setPerson}>
    <ContextMenuLabel>People</ContextMenuLabel>
    <ContextMenuRadioItem value="pedro">Pedro Duarte</ContextMenuRadioItem>
    <ContextMenuRadioItem value="colm">Colm Tuite</ContextMenuRadioItem>
  </ContextMenuRadioGroup>
</ContextMenuContent>

Table Row Context Menu

<ContextMenu>
  <ContextMenuTrigger asChild>
    <TableRow className="cursor-context-menu">
      <TableCell>{row.name}</TableCell>
      <TableCell>{row.status}</TableCell>
    </TableRow>
  </ContextMenuTrigger>
  <ContextMenuContent>
    <ContextMenuItem onSelect={() => editRow(row.id)}>
      <EditIcon size={13} /> Edit
    </ContextMenuItem>
    <ContextMenuItem onSelect={() => duplicateRow(row.id)}>
      <CopyIcon size={13} /> Duplicate
    </ContextMenuItem>
    <ContextMenuSeparator />
    <ContextMenuItem variant="destructive" onSelect={() => deleteRow(row.id)}>
      <TrashIcon size={13} /> Delete
    </ContextMenuItem>
  </ContextMenuContent>
</ContextMenu>

API Reference

ContextMenuContent

alignOffset
number
default:"0"
Offset from the cursor position on the alignment axis.
className
string
Additional class names for the menu panel.

ContextMenuItem

variant
string
default:"default"
Visual style. "destructive" renders item text in red.
disabled
boolean
default:"false"
Disables the item.
onSelect
() => void
Callback fired when item is selected. Menu closes automatically.

Accessibility

  • Context Menu opens at the cursor position with role="menu"
  • ContextMenuTrigger area does not show a visual indicator — consider adding a subtle hint for discoverability
  • Keyboard: Shift+F10 or the Menu key opens context menu at the focused element
  • All the same keyboard navigation as DropdownMenu applies (Arrow keys, Enter, Escape)

Do’s & Don’ts

Do

  • Use Context Menu for actions directly tied to the right-clicked element
  • Include keyboard shortcuts to mirror native OS conventions
  • Keep items to 5–8 max — only actions relevant to that specific element
  • Use on data-dense areas: table rows, file lists, canvas elements

Don't

  • Don’t rely solely on right-click — always provide primary action buttons too
  • Don’t use Context Menu as a main navigation mechanism
  • Don’t add items that aren’t contextually relevant to the trigger area
  • Don’t show a Context Menu on mobile without a fallback (long-press isn’t obvious)