Components
Context Menu
Displays a menu to the user, triggered by a right click.
Right click hereLong press here
Installation#
Install the dependencies
npm install cn radix-ui lucide-reactCopy the source into your project
components/ui/context-menu.tsx"use client"import * as React from "react"import { cn } from "cn"import { ContextMenu as ContextMenuPrimitive } from "radix-ui"import { ChevronRightIcon, CheckIcon } from "lucide-react"function ContextMenu({ ...props}: React.ComponentProps<typeof ContextMenuPrimitive.Root>) { return <ContextMenuPrimitive.Root data-slot="context-menu" {...props} />}function ContextMenuTrigger({ className, ...props}: React.ComponentProps<typeof ContextMenuPrimitive.Trigger>) { return ( <ContextMenuPrimitive.Trigger data-slot="context-menu-trigger" className={cn("select-none", className)} {...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 ContextMenuContent({ className, ...props}: React.ComponentProps<typeof ContextMenuPrimitive.Content> & { side?: "top" | "right" | "bottom" | "left"}) { return ( <ContextMenuPrimitive.Portal> <ContextMenuPrimitive.Content data-slot="context-menu-content" className={cn("z-50 max-h-(--radix-context-menu-content-available-height) min-w-36 origin-(--radix-context-menu-content-transform-origin) overflow-x-hidden overflow-y-auto rounded-xl bg-popover p-1 text-popover-foreground shadow-lg shadow-black/[0.06] ring-1 ring-overlay-border duration-100 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 data-open:animate-in data-open:fade-in-0 data-open:zoom-in-95 data-closed:animate-out data-closed:fade-out-0 data-closed:zoom-out-95", 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( "group/context-menu-item relative flex cursor-default items-center gap-1.5 rounded-lg px-1.5 py-1 text-sm outline-hidden select-none focus:bg-accent focus:text-accent-foreground data-inset:pl-7 data-[variant=destructive]:text-destructive data-[variant=destructive]:focus:bg-destructive/10 data-[variant=destructive]:focus:text-destructive dark:data-[variant=destructive]:focus:bg-destructive/20 data-disabled:pointer-events-none data-disabled:opacity-50 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4 focus:*:[svg]:text-accent-foreground data-[variant=destructive]:*:[svg]:text-destructive", className )} {...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( "flex cursor-default items-center gap-1.5 rounded-lg px-1.5 py-1 text-sm outline-hidden select-none focus:bg-accent focus:text-accent-foreground data-inset:pl-7 data-open:bg-accent data-open:text-accent-foreground [&_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("z-50 min-w-32 origin-(--radix-context-menu-content-transform-origin) overflow-hidden rounded-lg border bg-popover p-1 text-popover-foreground shadow-lg duration-100 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 data-open:animate-in data-open:fade-in-0 data-open:zoom-in-95 data-closed:animate-out data-closed:fade-out-0 data-closed:zoom-out-95", className )} {...props} /> )}function ContextMenuCheckboxItem({ className, children, checked, inset, ...props}: React.ComponentProps<typeof ContextMenuPrimitive.CheckboxItem> & { inset?: boolean}) { return ( <ContextMenuPrimitive.CheckboxItem data-slot="context-menu-checkbox-item" data-inset={inset} className={cn( "relative flex cursor-default items-center gap-1.5 rounded-lg py-1 pr-8 pl-1.5 text-sm outline-hidden select-none focus:bg-accent focus:text-accent-foreground data-inset:pl-7 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 right-2"> <ContextMenuPrimitive.ItemIndicator> <CheckIcon /> </ContextMenuPrimitive.ItemIndicator> </span> {children} </ContextMenuPrimitive.CheckboxItem> )}function ContextMenuRadioItem({ className, children, inset, ...props}: React.ComponentProps<typeof ContextMenuPrimitive.RadioItem> & { inset?: boolean}) { return ( <ContextMenuPrimitive.RadioItem data-slot="context-menu-radio-item" data-inset={inset} className={cn( "relative flex cursor-default items-center gap-1.5 rounded-lg py-1 pr-8 pl-1.5 text-sm outline-hidden select-none focus:bg-accent focus:text-accent-foreground data-inset:pl-7 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 right-2"> <ContextMenuPrimitive.ItemIndicator> <CheckIcon /> </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( "px-1.5 py-1 text-xs font-medium text-muted-foreground data-inset:pl-7", className )} {...props} /> )}function ContextMenuSeparator({ className, ...props}: React.ComponentProps<typeof ContextMenuPrimitive.Separator>) { return ( <ContextMenuPrimitive.Separator data-slot="context-menu-separator" className={cn("-mx-1 my-1 h-px bg-border", className)} {...props} /> )}function ContextMenuShortcut({ className, ...props}: React.ComponentProps<"span">) { return ( <span data-slot="context-menu-shortcut" className={cn( "ml-auto text-xs tracking-widest text-muted-foreground group-focus/context-menu-item:text-accent-foreground", className )} {...props} /> )}export { ContextMenu, ContextMenuTrigger, ContextMenuContent, ContextMenuItem, ContextMenuCheckboxItem, ContextMenuRadioItem, ContextMenuLabel, ContextMenuSeparator, ContextMenuShortcut, ContextMenuGroup, ContextMenuPortal, ContextMenuSub, ContextMenuSubContent, ContextMenuSubTrigger, ContextMenuRadioGroup,}Make sure the foundation is in place
Components read their colors, radius and surfaces from the tokens in
app/globals.css. See Theming.
Usage#
import { ContextMenu, ContextMenuTrigger, ContextMenuContent, ContextMenuItem, ContextMenuCheckboxItem, ContextMenuRadioItem, ContextMenuLabel, ContextMenuSeparator, ContextMenuShortcut, ContextMenuGroup, ContextMenuPortal, ContextMenuSub, ContextMenuSubContent, ContextMenuSubTrigger, ContextMenuRadioGroup,} from "@/components/ui/context-menu"Examples#
Basic
Right click hereLong press here
Checkboxes
Right click hereLong press here
Destructive
Right click hereLong press here
Groups
Right click hereLong press here
Icons
Right click hereLong press here
Radio
Right click hereLong press here
Shortcuts
Right click hereLong press here
Sides
Right click (top)Long press (top)Right click (right)Long press (right)Right click (bottom)Long press (bottom)Right click (left)Long press (left)
Submenu
Right click hereLong press here