shadcn.io is not affiliated with official shadcn/ui
Shadcn Context Menu for React and Tailwind
A menu shown on right-click.
Installation
bunx --bun shadcn@latest add https://kit.dev/r/context-menu.jsonpnpm dlx shadcn@latest add https://kit.dev/r/context-menu.jsonnpx shadcn@latest add https://kit.dev/r/context-menu.jsonyarn shadcn@latest add https://kit.dev/r/context-menu.jsonThis component depends on Menu. Install it first if you haven't already.
Install the following dependencies:
bun add @ark-ui/react lucide-react tailwind-variantspnpm add @ark-ui/react lucide-react tailwind-variantsnpm install @ark-ui/react lucide-react tailwind-variantsyarn add @ark-ui/react lucide-react tailwind-variantsCopy and paste the following code into your project.
"use client";
import {
Menu as ArkMenu,
useMenu as useArkMenu,
useMenuContext as useArkMenuContext,
useMenuItemContext as useArkMenuItemContext,
} from "@ark-ui/react/menu";
import type React from "react";
import { cn } from "@/lib/utils";
import {
Menu,
MenuArrow,
MenuCheckboxItem,
MenuContent,
MenuGroup,
MenuGroupLabel,
MenuItem,
MenuQuickItem,
MenuRadioGroup,
MenuRadioItem,
MenuSeparator,
MenuShortcut,
MenuSub,
MenuSubContent,
MenuSubTrigger,
} from "@/components/ui/menu";
export const useContextMenu = useArkMenu;
export const useContextMenuContext = useArkMenuContext;
export const useContextMenuItemContext = useArkMenuItemContext;
export const ContextMenuContext: typeof ArkMenu.Context = ArkMenu.Context;
export const ContextMenuItemContext: typeof ArkMenu.ItemContext =
ArkMenu.ItemContext;
export const ContextMenu = (props: React.ComponentProps<typeof Menu>) => {
const { positioning = { placement: "bottom-start" }, ...rest } = props;
return <Menu data-slot="context-menu" positioning={positioning} {...rest} />;
};
export const ContextMenuRootProvider = (
props: React.ComponentProps<typeof ArkMenu.RootProvider>
) => {
const { lazyMount = true, unmountOnExit = true, ...rest } = props;
return (
<ArkMenu.RootProvider
data-slot="context-menu"
lazyMount={lazyMount}
unmountOnExit={unmountOnExit}
{...rest}
/>
);
};
export const ContextMenuTrigger = (
props: React.ComponentProps<typeof ArkMenu.ContextTrigger>
) => {
const { children, className, ...rest } = props;
const child =
Array.isArray(children) && children.length === 1 ? children[0] : children;
return (
<ArkMenu.ContextTrigger
className={cn("cursor-default", className)}
data-slot="context-menu-trigger"
{...rest}
>
{child}
</ArkMenu.ContextTrigger>
);
};
export const ContextMenuContent = (
props: React.ComponentProps<typeof MenuContent>
) => <MenuContent data-slot="context-menu-content" {...props} />;
export const ContextMenuGroup = (
props: React.ComponentProps<typeof MenuGroup>
) => <MenuGroup data-slot="context-menu-group" {...props} />;
export const ContextMenuGroupLabel = (
props: React.ComponentProps<typeof MenuGroupLabel>
) => <MenuGroupLabel data-slot="context-menu-group-label" {...props} />;
export const ContextMenuSeparator = (
props: React.ComponentProps<typeof MenuSeparator>
) => <MenuSeparator data-slot="context-menu-separator" {...props} />;
export const ContextMenuItem = (
props: React.ComponentProps<typeof MenuItem>
) => <MenuItem data-slot="context-menu-item" {...props} />;
export const ContextMenuQuickItem = (
props: React.ComponentProps<typeof MenuQuickItem>
) => <MenuQuickItem data-slot="context-menu-quick-item" {...props} />;
export const ContextMenuCheckboxItem = (
props: React.ComponentProps<typeof MenuCheckboxItem>
) => <MenuCheckboxItem data-slot="context-menu-checkbox-item" {...props} />;
export const ContextMenuRadioGroup = (
props: React.ComponentProps<typeof MenuRadioGroup>
) => <MenuRadioGroup data-slot="context-menu-radio-group" {...props} />;
export const ContextMenuRadioItem = (
props: React.ComponentProps<typeof MenuRadioItem>
) => <MenuRadioItem data-slot="context-menu-radio-item" {...props} />;
export const ContextMenuSub = (props: React.ComponentProps<typeof MenuSub>) => (
<MenuSub data-slot="context-menu-sub" {...props} />
);
export const ContextMenuSubContent = (
props: React.ComponentProps<typeof MenuSubContent>
) => <MenuSubContent data-slot="context-menu-sub-content" {...props} />;
export const ContextMenuSubTrigger = (
props: React.ComponentProps<typeof MenuSubTrigger>
) => <MenuSubTrigger data-slot="context-menu-sub-trigger" {...props} />;
export const ContextMenuShortcut = (
props: React.ComponentProps<typeof MenuShortcut>
) => <MenuShortcut data-slot="context-menu-shortcut" {...props} />;
export const ContextMenuArrow = (
props: React.ComponentProps<typeof MenuArrow>
) => <MenuArrow data-slot="context-menu-arrow" {...props} />;Update the import paths to match your project setup.
Anatomy
ContextMenuArrow
└── ContextMenuArrowTip
ContextMenuContent
ContextMenuTrigger
ContextMenuIndicator
ContextMenuItem
├── ContextMenuItemGroup
│ └── ContextMenuItemGroupLabel
├── ContextMenuItemIndicator
└── ContextMenuItemText
ContextMenuPositioner
ContextMenuSeparator
ContextMenuTrigger
└── ContextMenuTriggerItemUsage
import {
Archive,
ArchiveX,
Bell,
CirclePlus,
FolderInput,
MailX,
Reply,
ReplyAll,
Send,
SquarePen,
Trash,
Trash2,
} from "lucide-react";
import {
ContextMenu,
ContextMenuContent,
ContextMenuGroup,
ContextMenuItem,
ContextMenuSeparator,
ContextMenuShortcut,
ContextMenuSub,
ContextMenuSubContent,
ContextMenuSubTrigger,
ContextMenuTrigger,
} from "@/components/ui/context-menu";<ContextMenu>
<ContextMenuTrigger className="flex aspect-video items-center justify-center rounded-2xl border border-dashed p-20 text-sm">
<span className="pointer-fine:inline-block hidden">Right click here</span>
<span className="pointer-coarse:inline-block hidden">
Long press here
</span>
</ContextMenuTrigger>
<ContextMenuContent className="w-40">
<ContextMenuGroup>
<ContextMenuItem value="forward">
<Send aria-hidden="true" /> Forward
<ContextMenuShortcut>⌘F</ContextMenuShortcut>
</ContextMenuItem>
<ContextMenuItem value="reply">
<Reply aria-hidden="true" /> Reply
<ContextMenuShortcut>⌘R</ContextMenuShortcut>
</ContextMenuItem>
<ContextMenuItem value="reply-all">
<ReplyAll aria-hidden="true" /> Reply all
<ContextMenuShortcut>⌘A</ContextMenuShortcut>
</ContextMenuItem>
<ContextMenuItem value="archive">
<Archive aria-hidden="true" /> Archive
<ContextMenuShortcut>⌘Z</ContextMenuShortcut>
</ContextMenuItem>
<ContextMenuSub>
<ContextMenuSubTrigger>
<FolderInput aria-hidden="true" /> Move to
</ContextMenuSubTrigger>
<ContextMenuSubContent>
<ContextMenuItem value="move-to-folder-1">
<ArchiveX aria-hidden="true" /> Junk
</ContextMenuItem>
<ContextMenuItem value="move-to-folder-2">
<Trash aria-hidden="true" /> Trash
</ContextMenuItem>
<ContextMenuItem value="move-to-folder-3">
<Bell aria-hidden="true" /> Reminders
</ContextMenuItem>
<ContextMenuSub>
<ContextMenuSubTrigger>
<CirclePlus aria-hidden="true" />
More
</ContextMenuSubTrigger>
<ContextMenuSubContent>
<ContextMenuItem value="move-to-folder-4">
<SquarePen aria-hidden="true" />
Drafts
</ContextMenuItem>
<ContextMenuItem value="move-to-folder-6">
<MailX aria-hidden="true" />
Spam
</ContextMenuItem>
</ContextMenuSubContent>
</ContextMenuSub>
</ContextMenuSubContent>
</ContextMenuSub>
<ContextMenuSeparator />
<ContextMenuItem value="delete" variant="destructive">
<Trash2 aria-hidden="true" /> Delete
<ContextMenuShortcut>⌘ ⌫</ContextMenuShortcut>
</ContextMenuItem>
</ContextMenuGroup>
</ContextMenuContent>
</ContextMenu>Accessibility
| Key | Description |
|---|---|
Enter | Activate the focused control. |
Space | Activate the focused control. |
ArrowDown | Move down. |
ArrowUp | Move up. |
ArrowLeft | Move left. |
ArrowRight | Move right. |
Home | Go to the start. |
End | Go to the end. |
Demos
Checkboxes
Context
Controlled
Destructive
Dialog
Group Label
Icons
Link
Nested
Quick Item
Radio Group
Root Provider
Shortcuts
With Scroll
With Separator
API Reference
ContextMenuTrigger
button
PropType