Shadcn Context Menu
Free shadcn/ui context menu for React — a right-click menu built on Radix: items and shortcuts, a nested submenu, checkbox items, a radio group, and destructive file actions.
Installation
{
"registries": {
"@designrevision": "https://registry.designrevision.com/r/{name}.json"
}
}
Add to your existing components.json. Requires shadcn/ui v2.3+.
Packages
Props
No props documented yet.
"use client"
import * as React from "react"
import * as ContextMenuPrimitive from "@radix-ui/react-context-menu"
import { CheckIcon, ChevronRightIcon, CircleIcon } from "lucide-react"
import { cn } from "@/lib/utils"
function ContextMenu({
...props
}: React.ComponentProps<typeof ContextMenuPrimitive.Root>) {
return <ContextMenuPrimitive.Root data-slot="context-menu" {...props} />
}
function ContextMenuTrigger({
...props
}: React.ComponentProps<typeof ContextMenuPrimitive.Trigger>) {
return (
<ContextMenuPrimitive.Trigger data-slot="context-menu-trigger" {...props} />
)
}
function ContextMenuGroup({
...props
}: React.ComponentProps<typeof ContextMenuPrimitive.Group>) {
return (
<ContextMenuPrimitive.Group data-slot="context-menu-group" {...props} />
)
}
function ContextMenuPortal({
...props
}: React.ComponentProps<typeof ContextMenuPrimitive.Portal>) {
return (
<ContextMenuPrimitive.Portal data-slot="context-menu-portal" {...props} />
)
}
function ContextMenuSub({
...props
}: React.ComponentProps<typeof ContextMenuPrimitive.Sub>) {
return <ContextMenuPrimitive.Sub data-slot="context-menu-sub" {...props} />
}
function ContextMenuRadioGroup({
...props
}: React.ComponentProps<typeof ContextMenuPrimitive.RadioGroup>) {
return (
<ContextMenuPrimitive.RadioGroup
data-slot="context-menu-radio-group"
{...props}
/>
)
}
function ContextMenuSubTrigger({
className,
inset,
children,
...props
}: React.ComponentProps<typeof ContextMenuPrimitive.SubTrigger> & {
inset?: boolean
}) {
return (
<ContextMenuPrimitive.SubTrigger
data-slot="context-menu-sub-trigger"
data-inset={inset}
className={cn(
"focus:bg-accent focus:text-accent-foreground data-[state=open]:bg-accent data-[state=open]:text-accent-foreground flex cursor-default items-center rounded-sm px-2 py-1.5 text-sm outline-hidden select-none data-[inset]:pl-8 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4",
className
)}
{...props}
>
{children}
<ChevronRightIcon className="ml-auto" />
</ContextMenuPrimitive.SubTrigger>
)
}
function ContextMenuSubContent({
className,
...props
}: React.ComponentProps<typeof ContextMenuPrimitive.SubContent>) {
return (
<ContextMenuPrimitive.SubContent
data-slot="context-menu-sub-content"
className={cn(
"bg-popover text-popover-foreground data-[state=open]:animate-in data-[state=closed]:animate-out 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 z-50 min-w-[8rem] origin-(--radix-context-menu-content-transform-origin) overflow-hidden rounded-md border p-1 shadow-lg",
className
)}
{...props}
/>
)
}
function ContextMenuContent({
className,
...props
}: React.ComponentProps<typeof ContextMenuPrimitive.Content>) {
return (
<ContextMenuPrimitive.Portal>
<ContextMenuPrimitive.Content
data-slot="context-menu-content"
className={cn(
"bg-popover text-popover-foreground data-[state=open]:animate-in data-[state=closed]:animate-out 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 z-50 max-h-(--radix-context-menu-content-available-height) min-w-[8rem] origin-(--radix-context-menu-content-transform-origin) overflow-x-hidden overflow-y-auto rounded-md border p-1 shadow-md",
className
)}
{...props}
/>
</ContextMenuPrimitive.Portal>
)
}
function ContextMenuItem({
className,
inset,
variant = "default",
...props
}: React.ComponentProps<typeof ContextMenuPrimitive.Item> & {
inset?: boolean
variant?: "default" | "destructive"
}) {
return (
<ContextMenuPrimitive.Item
data-slot="context-menu-item"
data-inset={inset}
data-variant={variant}
className={cn(
"focus:bg-accent focus:text-accent-foreground data-[variant=destructive]:text-destructive data-[variant=destructive]:focus:bg-destructive/10 dark:data-[variant=destructive]:focus:bg-destructive/20 data-[variant=destructive]:focus:text-destructive data-[variant=destructive]:*:[svg]:!text-destructive [&_svg:not([class*='text-'])]:text-muted-foreground relative flex cursor-default items-center gap-2 rounded-sm px-2 py-1.5 text-sm outline-hidden select-none data-[disabled]:pointer-events-none data-[disabled]:opacity-50 data-[inset]:pl-8 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4",
className
)}
{...props}
/>
)
}
function ContextMenuCheckboxItem({
className,
children,
checked,
...props
}: React.ComponentProps<typeof ContextMenuPrimitive.CheckboxItem>) {
return (
<ContextMenuPrimitive.CheckboxItem
data-slot="context-menu-checkbox-item"
className={cn(
"focus:bg-accent focus:text-accent-foreground relative flex cursor-default items-center gap-2 rounded-sm py-1.5 pr-2 pl-8 text-sm outline-hidden select-none data-[disabled]:pointer-events-none data-[disabled]:opacity-50 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4",
className
)}
checked={checked}
{...props}
>
<span className="pointer-events-none absolute left-2 flex size-3.5 items-center justify-center">
<ContextMenuPrimitive.ItemIndicator>
<CheckIcon className="size-4" />
</ContextMenuPrimitive.ItemIndicator>
</span>
{children}
</ContextMenuPrimitive.CheckboxItem>
)
}
function ContextMenuRadioItem({
className,
children,
...props
}: React.ComponentProps<typeof ContextMenuPrimitive.RadioItem>) {
return (
<ContextMenuPrimitive.RadioItem
data-slot="context-menu-radio-item"
className={cn(
"focus:bg-accent focus:text-accent-foreground relative flex cursor-default items-center gap-2 rounded-sm py-1.5 pr-2 pl-8 text-sm outline-hidden select-none data-[disabled]:pointer-events-none data-[disabled]:opacity-50 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4",
className
)}
{...props}
>
<span className="pointer-events-none absolute left-2 flex size-3.5 items-center justify-center">
<ContextMenuPrimitive.ItemIndicator>
<CircleIcon className="size-2 fill-current" />
</ContextMenuPrimitive.ItemIndicator>
</span>
{children}
</ContextMenuPrimitive.RadioItem>
)
}
function ContextMenuLabel({
className,
inset,
...props
}: React.ComponentProps<typeof ContextMenuPrimitive.Label> & {
inset?: boolean
}) {
return (
<ContextMenuPrimitive.Label
data-slot="context-menu-label"
data-inset={inset}
className={cn(
"text-foreground px-2 py-1.5 text-sm font-medium data-[inset]:pl-8",
className
)}
{...props}
/>
)
}
function ContextMenuSeparator({
className,
...props
}: React.ComponentProps<typeof ContextMenuPrimitive.Separator>) {
return (
<ContextMenuPrimitive.Separator
data-slot="context-menu-separator"
className={cn("bg-border -mx-1 my-1 h-px", className)}
{...props}
/>
)
}
function ContextMenuShortcut({
className,
...props
}: React.ComponentProps<"span">) {
return (
<span
data-slot="context-menu-shortcut"
className={cn(
"text-muted-foreground ml-auto text-xs tracking-widest",
className
)}
{...props}
/>
)
}
export {
ContextMenu,
ContextMenuTrigger,
ContextMenuContent,
ContextMenuItem,
ContextMenuCheckboxItem,
ContextMenuRadioItem,
ContextMenuLabel,
ContextMenuSeparator,
ContextMenuShortcut,
ContextMenuGroup,
ContextMenuPortal,
ContextMenuSub,
ContextMenuSubContent,
ContextMenuSubTrigger,
ContextMenuRadioGroup,
}
Examples
Basic
The canonical right-click menu — items, shortcuts, and a separator.
Packages
Props
No props documented yet.
import {
ContextMenu,
ContextMenuContent,
ContextMenuItem,
ContextMenuSeparator,
ContextMenuShortcut,
ContextMenuTrigger,
} from "@/components/ui/context-menu"
export default function ContextMenuDemo01() {
return (
<ContextMenu>
<ContextMenuTrigger className="text-muted-foreground flex h-40 w-full max-w-sm items-center justify-center rounded-md border border-dashed text-sm">
Right-click here
</ContextMenuTrigger>
<ContextMenuContent className="w-52">
<ContextMenuItem>
Back
<ContextMenuShortcut>⌘[</ContextMenuShortcut>
</ContextMenuItem>
<ContextMenuItem disabled>
Forward
<ContextMenuShortcut>⌘]</ContextMenuShortcut>
</ContextMenuItem>
<ContextMenuItem>
Reload
<ContextMenuShortcut>⌘R</ContextMenuShortcut>
</ContextMenuItem>
<ContextMenuSeparator />
<ContextMenuItem>
Save As…
<ContextMenuShortcut>⌘S</ContextMenuShortcut>
</ContextMenuItem>
<ContextMenuItem>
Print…
<ContextMenuShortcut>⌘P</ContextMenuShortcut>
</ContextMenuItem>
</ContextMenuContent>
</ContextMenu>
)
}
With submenu
A nested submenu opened from a parent item.
Packages
Props
No props documented yet.
import {
ContextMenu,
ContextMenuContent,
ContextMenuItem,
ContextMenuSeparator,
ContextMenuShortcut,
ContextMenuSub,
ContextMenuSubContent,
ContextMenuSubTrigger,
ContextMenuTrigger,
} from "@/components/ui/context-menu"
export default function ContextMenuSubmenu01() {
return (
<ContextMenu>
<ContextMenuTrigger className="text-muted-foreground flex h-40 w-full max-w-sm items-center justify-center rounded-md border border-dashed text-sm">
Right-click here
</ContextMenuTrigger>
<ContextMenuContent className="w-52">
<ContextMenuItem>
New Tab
<ContextMenuShortcut>⌘T</ContextMenuShortcut>
</ContextMenuItem>
<ContextMenuItem>
New Window
<ContextMenuShortcut>⌘N</ContextMenuShortcut>
</ContextMenuItem>
<ContextMenuSub>
<ContextMenuSubTrigger>More Tools</ContextMenuSubTrigger>
<ContextMenuSubContent className="w-44">
<ContextMenuItem>Save Page As…</ContextMenuItem>
<ContextMenuItem>Create Shortcut…</ContextMenuItem>
<ContextMenuSeparator />
<ContextMenuItem>Developer Tools</ContextMenuItem>
</ContextMenuSubContent>
</ContextMenuSub>
<ContextMenuSeparator />
<ContextMenuItem>
Print…
<ContextMenuShortcut>⌘P</ContextMenuShortcut>
</ContextMenuItem>
</ContextMenuContent>
</ContextMenu>
)
}
Checkbox items
Toggleable checkbox items grouped under a label.
Packages
Props
No props documented yet.
"use client"
import * as React from "react"
import {
ContextMenu,
ContextMenuCheckboxItem,
ContextMenuContent,
ContextMenuLabel,
ContextMenuSeparator,
ContextMenuTrigger,
} from "@/components/ui/context-menu"
export default function ContextMenuCheckbox01() {
const [showBookmarks, setShowBookmarks] = React.useState(true)
const [showFullUrls, setShowFullUrls] = React.useState(false)
return (
<ContextMenu>
<ContextMenuTrigger className="text-muted-foreground flex h-40 w-full max-w-sm items-center justify-center rounded-md border border-dashed text-sm">
Right-click here
</ContextMenuTrigger>
<ContextMenuContent className="w-56">
<ContextMenuLabel>Appearance</ContextMenuLabel>
<ContextMenuSeparator />
<ContextMenuCheckboxItem
checked={showBookmarks}
onCheckedChange={setShowBookmarks}
>
Show Bookmarks Bar
</ContextMenuCheckboxItem>
<ContextMenuCheckboxItem
checked={showFullUrls}
onCheckedChange={setShowFullUrls}
>
Show Full URLs
</ContextMenuCheckboxItem>
</ContextMenuContent>
</ContextMenu>
)
}
Radio items
A single-choice radio group inside the menu.
Packages
Props
No props documented yet.
"use client"
import * as React from "react"
import {
ContextMenu,
ContextMenuContent,
ContextMenuLabel,
ContextMenuRadioGroup,
ContextMenuRadioItem,
ContextMenuSeparator,
ContextMenuTrigger,
} from "@/components/ui/context-menu"
export default function ContextMenuRadio01() {
const [person, setPerson] = React.useState("pedro")
return (
<ContextMenu>
<ContextMenuTrigger className="text-muted-foreground flex h-40 w-full max-w-sm items-center justify-center rounded-md border border-dashed text-sm">
Right-click here
</ContextMenuTrigger>
<ContextMenuContent className="w-56">
<ContextMenuLabel>Assign to</ContextMenuLabel>
<ContextMenuSeparator />
<ContextMenuRadioGroup value={person} onValueChange={setPerson}>
<ContextMenuRadioItem value="pedro">
Pedro Duarte
</ContextMenuRadioItem>
<ContextMenuRadioItem value="colm">Colm Tuite</ContextMenuRadioItem>
<ContextMenuRadioItem value="ada">Ada Lovelace</ContextMenuRadioItem>
</ContextMenuRadioGroup>
</ContextMenuContent>
</ContextMenu>
)
}
File actions
A file-row menu with icons and a destructive delete item.
Packages
Props
No props documented yet.
import { Copy, Download, Pencil, Trash2 } from "lucide-react"
import {
ContextMenu,
ContextMenuContent,
ContextMenuItem,
ContextMenuSeparator,
ContextMenuShortcut,
ContextMenuTrigger,
} from "@/components/ui/context-menu"
export default function ContextMenuDestructive01() {
return (
<ContextMenu>
<ContextMenuTrigger className="flex h-40 w-full max-w-sm flex-col items-center justify-center gap-2 rounded-md border border-dashed text-sm">
<span className="font-medium">document.pdf</span>
<span className="text-muted-foreground text-xs">Right-click the file</span>
</ContextMenuTrigger>
<ContextMenuContent className="w-52">
<ContextMenuItem>
<Pencil />
Rename
<ContextMenuShortcut>⌘E</ContextMenuShortcut>
</ContextMenuItem>
<ContextMenuItem>
<Copy />
Duplicate
</ContextMenuItem>
<ContextMenuItem>
<Download />
Download
</ContextMenuItem>
<ContextMenuSeparator />
<ContextMenuItem variant="destructive">
<Trash2 />
Delete
<ContextMenuShortcut>⌫</ContextMenuShortcut>
</ContextMenuItem>
</ContextMenuContent>
</ContextMenu>
)
}
The Shadcn Context Menu is a right-click menu for React, built on Radix. Wrap a region in a trigger, and right-clicking it — or long-pressing on touch — opens a menu of contextual actions at the pointer. Radix handles positioning, keyboard navigation, focus, and ARIA. The examples below cover a basic menu, a submenu, checkbox items, a radio group, and destructive file actions.
The shape
<ContextMenu>
<ContextMenuTrigger>Right-click here</ContextMenuTrigger>
<ContextMenuContent>
<ContextMenuItem>
Back <ContextMenuShortcut>⌘[</ContextMenuShortcut>
</ContextMenuItem>
<ContextMenuSeparator />
<ContextMenuItem>Reload</ContextMenuItem>
</ContextMenuContent>
</ContextMenu>
The ContextMenuTrigger is the region you right-click; ContextMenuContent is the menu that appears. ContextMenuItem, ContextMenuSeparator, and ContextMenuShortcut build the list.
Context Menu vs Dropdown Menu
They share the same item parts but open differently:
- Dropdown Menu — opens from a button you click.
- Context Menu — opens on right-click (or long-press) of a region, with no visible button.
Use a context menu for contextual actions on an item — a file, a table row, a canvas; use a dropdown for a menu button.
Submenus
Nest a ContextMenuSub for a second level:
<ContextMenuSub>
<ContextMenuSubTrigger>More tools</ContextMenuSubTrigger>
<ContextMenuSubContent>{/* … */}</ContextMenuSubContent>
</ContextMenuSub>
The trigger shows a chevron and opens on hover or arrow-right.
Checkbox and radio items
For stateful menus, use checkbox items (independent toggles) or a radio group (single choice):
<ContextMenuCheckboxItem checked={showBookmarks} onCheckedChange={setShowBookmarks}>
Show Bookmarks
</ContextMenuCheckboxItem>
<ContextMenuRadioGroup value={pos} onValueChange={setPos}>
<ContextMenuRadioItem value="top">Top</ContextMenuRadioItem>
<ContextMenuRadioItem value="bottom">Bottom</ContextMenuRadioItem>
</ContextMenuRadioGroup>
Destructive actions
Mark a dangerous item with variant="destructive" so it reads as risky:
<ContextMenuItem variant="destructive">
<Trash2 /> Delete
</ContextMenuItem>
Accessibility
Radix wires up full keyboard navigation (arrows, Enter, Escape), focus management, and ARIA roles, and opens on long-press on touch. Because right-click isn't discoverable for everyone, reserve context menus for power-user shortcuts and make the same actions reachable another way — a row menu button or a toolbar.