Context Menu
Right-click menu with items, checkboxes, radio groups, submenus, and shortcuts.
Examples
#Basic
#A simple context menu with a few actions.
import {
ContextMenu,
ContextMenuContent,
ContextMenuGroup,
ContextMenuItem,
ContextMenuTrigger,
} from "@codefast/ui/context-menu";
export function ContextMenuBasic() {
return (
<ContextMenu>
<ContextMenuTrigger className="flex aspect-video w-full max-w-xs items-center justify-center rounded-xl border border-dashed text-sm">
<span className="hidden pointer-fine:inline-block">Right click here</span>
<span className="hidden pointer-coarse:inline-block">Long press here</span>
</ContextMenuTrigger>
<ContextMenuContent>
<ContextMenuGroup>
<ContextMenuItem>Back</ContextMenuItem>
<ContextMenuItem disabled>Forward</ContextMenuItem>
<ContextMenuItem>Reload</ContextMenuItem>
</ContextMenuGroup>
</ContextMenuContent>
</ContextMenu>
);
}
Checkboxes
#Use ContextMenuCheckboxItem for toggles.
import {
ContextMenu,
ContextMenuCheckboxItem,
ContextMenuContent,
ContextMenuGroup,
ContextMenuTrigger,
} from "@codefast/ui/context-menu";
export function ContextMenuCheckboxes() {
return (
<ContextMenu>
<ContextMenuTrigger className="flex aspect-video w-full max-w-xs items-center justify-center rounded-xl border border-dashed text-sm">
<span className="hidden pointer-fine:inline-block">Right click here</span>
<span className="hidden pointer-coarse:inline-block">Long press here</span>
</ContextMenuTrigger>
<ContextMenuContent>
<ContextMenuGroup>
<ContextMenuCheckboxItem defaultChecked>Show Bookmarks Bar</ContextMenuCheckboxItem>
<ContextMenuCheckboxItem>Show Full URLs</ContextMenuCheckboxItem>
<ContextMenuCheckboxItem defaultChecked>Show Developer Tools</ContextMenuCheckboxItem>
</ContextMenuGroup>
</ContextMenuContent>
</ContextMenu>
);
}
Destructive
#Use variant='destructive' to style the menu item as destructive.
import {
ContextMenu,
ContextMenuContent,
ContextMenuGroup,
ContextMenuItem,
ContextMenuSeparator,
ContextMenuTrigger,
} from "@codefast/ui/context-menu";
import { PencilIcon, ShareIcon, TrashIcon } from "lucide-react";
export function ContextMenuDestructive() {
return (
<ContextMenu>
<ContextMenuTrigger className="flex aspect-video w-full max-w-xs items-center justify-center rounded-xl border border-dashed text-sm">
<span className="hidden pointer-fine:inline-block">Right click here</span>
<span className="hidden pointer-coarse:inline-block">Long press here</span>
</ContextMenuTrigger>
<ContextMenuContent>
<ContextMenuGroup>
<ContextMenuItem>
<PencilIcon />
Edit
</ContextMenuItem>
<ContextMenuItem>
<ShareIcon />
Share
</ContextMenuItem>
</ContextMenuGroup>
<ContextMenuSeparator />
<ContextMenuGroup>
<ContextMenuItem variant="destructive">
<TrashIcon />
Delete
</ContextMenuItem>
</ContextMenuGroup>
</ContextMenuContent>
</ContextMenu>
);
}
Groups
#Group related actions and separate them with dividers.
import {
ContextMenu,
ContextMenuContent,
ContextMenuGroup,
ContextMenuItem,
ContextMenuLabel,
ContextMenuSeparator,
ContextMenuShortcut,
ContextMenuTrigger,
} from "@codefast/ui/context-menu";
export function ContextMenuGroups() {
return (
<ContextMenu>
<ContextMenuTrigger className="flex aspect-video w-full max-w-xs items-center justify-center rounded-xl border border-dashed text-sm">
<span className="hidden pointer-fine:inline-block">Right click here</span>
<span className="hidden pointer-coarse:inline-block">Long press here</span>
</ContextMenuTrigger>
<ContextMenuContent>
<ContextMenuGroup>
<ContextMenuLabel>File</ContextMenuLabel>
<ContextMenuItem>
New File
<ContextMenuShortcut>⌘N</ContextMenuShortcut>
</ContextMenuItem>
<ContextMenuItem>
Open File
<ContextMenuShortcut>⌘O</ContextMenuShortcut>
</ContextMenuItem>
<ContextMenuItem>
Save
<ContextMenuShortcut>⌘S</ContextMenuShortcut>
</ContextMenuItem>
</ContextMenuGroup>
<ContextMenuSeparator />
<ContextMenuGroup>
<ContextMenuLabel>Edit</ContextMenuLabel>
<ContextMenuItem>
Undo
<ContextMenuShortcut>⌘Z</ContextMenuShortcut>
</ContextMenuItem>
<ContextMenuItem>
Redo
<ContextMenuShortcut>⇧⌘Z</ContextMenuShortcut>
</ContextMenuItem>
</ContextMenuGroup>
<ContextMenuSeparator />
<ContextMenuGroup>
<ContextMenuItem>
Cut
<ContextMenuShortcut>⌘X</ContextMenuShortcut>
</ContextMenuItem>
<ContextMenuItem>
Copy
<ContextMenuShortcut>⌘C</ContextMenuShortcut>
</ContextMenuItem>
<ContextMenuItem>
Paste
<ContextMenuShortcut>⌘V</ContextMenuShortcut>
</ContextMenuItem>
</ContextMenuGroup>
<ContextMenuSeparator />
<ContextMenuGroup>
<ContextMenuItem variant="destructive">
Delete
<ContextMenuShortcut>⌫</ContextMenuShortcut>
</ContextMenuItem>
</ContextMenuGroup>
</ContextMenuContent>
</ContextMenu>
);
}
Icons
#Combine icons with labels for quick scanning.
import {
ContextMenu,
ContextMenuContent,
ContextMenuGroup,
ContextMenuItem,
ContextMenuSeparator,
ContextMenuTrigger,
} from "@codefast/ui/context-menu";
import { ClipboardPasteIcon, CopyIcon, ScissorsIcon, TrashIcon } from "lucide-react";
export function ContextMenuIcons() {
return (
<ContextMenu>
<ContextMenuTrigger className="flex aspect-video w-full max-w-xs items-center justify-center rounded-xl border border-dashed text-sm">
<span className="hidden pointer-fine:inline-block">Right click here</span>
<span className="hidden pointer-coarse:inline-block">Long press here</span>
</ContextMenuTrigger>
<ContextMenuContent>
<ContextMenuGroup>
<ContextMenuItem>
<CopyIcon />
Copy
</ContextMenuItem>
<ContextMenuItem>
<ScissorsIcon />
Cut
</ContextMenuItem>
<ContextMenuItem>
<ClipboardPasteIcon />
Paste
</ContextMenuItem>
</ContextMenuGroup>
<ContextMenuSeparator />
<ContextMenuGroup>
<ContextMenuItem variant="destructive">
<TrashIcon />
Delete
</ContextMenuItem>
</ContextMenuGroup>
</ContextMenuContent>
</ContextMenu>
);
}
Radio
#Use ContextMenuRadioItem for exclusive choices.
import {
ContextMenu,
ContextMenuContent,
ContextMenuGroup,
ContextMenuLabel,
ContextMenuRadioGroup,
ContextMenuRadioItem,
ContextMenuSeparator,
ContextMenuTrigger,
} from "@codefast/ui/context-menu";
import * as React from "react";
export function ContextMenuRadio() {
const [user, setUser] = React.useState("pedro");
const [theme, setTheme] = React.useState("light");
return (
<ContextMenu>
<ContextMenuTrigger className="flex aspect-video w-full max-w-xs items-center justify-center rounded-xl border border-dashed text-sm">
<span className="hidden pointer-fine:inline-block">Right click here</span>
<span className="hidden pointer-coarse:inline-block">Long press here</span>
</ContextMenuTrigger>
<ContextMenuContent>
<ContextMenuGroup>
<ContextMenuLabel>People</ContextMenuLabel>
<ContextMenuRadioGroup value={user} onValueChange={setUser}>
<ContextMenuRadioItem value="pedro">Pedro Duarte</ContextMenuRadioItem>
<ContextMenuRadioItem value="colm">Colm Tuite</ContextMenuRadioItem>
</ContextMenuRadioGroup>
</ContextMenuGroup>
<ContextMenuSeparator />
<ContextMenuGroup>
<ContextMenuLabel>Theme</ContextMenuLabel>
<ContextMenuRadioGroup value={theme} onValueChange={setTheme}>
<ContextMenuRadioItem value="light">Light</ContextMenuRadioItem>
<ContextMenuRadioItem value="dark">Dark</ContextMenuRadioItem>
<ContextMenuRadioItem value="system">System</ContextMenuRadioItem>
</ContextMenuRadioGroup>
</ContextMenuGroup>
</ContextMenuContent>
</ContextMenu>
);
}
RTL
#Right-to-left layout support for languages such as Arabic and Hebrew.
Translations are AI-generated for demonstration and may be imperfect.
import {
ContextMenu,
ContextMenuCheckboxItem,
ContextMenuContent,
ContextMenuGroup,
ContextMenuItem,
ContextMenuLabel,
ContextMenuRadioGroup,
ContextMenuRadioItem,
ContextMenuSeparator,
ContextMenuShortcut,
ContextMenuSub,
ContextMenuSubContent,
ContextMenuSubTrigger,
ContextMenuTrigger,
} from "@codefast/ui/context-menu";
import { ArrowLeftIcon, ArrowRightIcon, RotateCwIcon } from "lucide-react";
import * as React from "react";
import type { Translations } from "#/features/components-catalog/components/detail/language";
import { useTranslation } from "#/features/components-catalog/components/detail/language-context";
const translations: Translations = {
en: {
dir: "ltr",
values: {
rightClick: "Right click here",
longPress: "Long press here",
navigation: "Navigation",
back: "Back",
forward: "Forward",
reload: "Reload",
moreTools: "More Tools",
savePage: "Save Page...",
createShortcut: "Create Shortcut...",
nameWindow: "Name Window...",
developerTools: "Developer Tools",
delete: "Delete",
showBookmarks: "Show Bookmarks",
showFullUrls: "Show Full URLs",
people: "People",
pedro: "Pedro Duarte",
colm: "Colm Tuite",
},
},
ar: {
dir: "rtl",
values: {
rightClick: "انقر بزر الماوس الأيمن هنا",
longPress: "اضغط مطولاً هنا",
navigation: "التنقل",
back: "رجوع",
forward: "تقدم",
reload: "إعادة تحميل",
moreTools: "المزيد من الأدوات",
savePage: "حفظ الصفحة...",
createShortcut: "إنشاء اختصار...",
nameWindow: "تسمية النافذة...",
developerTools: "أدوات المطور",
delete: "حذف",
showBookmarks: "إظهار الإشارات المرجعية",
showFullUrls: "إظهار عناوين URL الكاملة",
people: "الأشخاص",
pedro: "Pedro Duarte",
colm: "Colm Tuite",
},
},
he: {
dir: "rtl",
values: {
rightClick: "לחץ לחיצה ימנית כאן",
longPress: "לחץ לחיצה ארוכה כאן",
navigation: "ניווט",
back: "חזור",
forward: "קדימה",
reload: "רענן",
moreTools: "כלים נוספים",
savePage: "שמור עמוד...",
createShortcut: "צור קיצור דרך...",
nameWindow: "שם חלון...",
developerTools: "כלי מפתח",
delete: "מחק",
showBookmarks: "הצג סימניות",
showFullUrls: "הצג כתובות URL מלאות",
people: "אנשים",
pedro: "Pedro Duarte",
colm: "Colm Tuite",
},
},
};
export function ContextMenuRtl() {
const { t, dir, language } = useTranslation(translations, "ar");
const [people, setPeople] = React.useState("pedro");
return (
<ContextMenu>
<ContextMenuTrigger className="flex aspect-video w-full max-w-xs items-center justify-center rounded-xl border border-dashed text-sm">
<span className="hidden pointer-fine:inline-block">{t.rightClick}</span>
<span className="hidden pointer-coarse:inline-block">{t.longPress}</span>
</ContextMenuTrigger>
<ContextMenuContent className="w-48" data-lang={dir === "rtl" ? language : undefined}>
<ContextMenuGroup>
<ContextMenuSub>
<ContextMenuSubTrigger>{t.navigation}</ContextMenuSubTrigger>
<ContextMenuSubContent className="w-44" data-lang={dir === "rtl" ? language : undefined}>
<ContextMenuGroup>
<ContextMenuItem>
<ArrowLeftIcon />
{t.back}
<ContextMenuShortcut>⌘[</ContextMenuShortcut>
</ContextMenuItem>
<ContextMenuItem disabled>
<ArrowRightIcon />
{t.forward}
<ContextMenuShortcut>⌘]</ContextMenuShortcut>
</ContextMenuItem>
<ContextMenuItem>
<RotateCwIcon />
{t.reload}
<ContextMenuShortcut>⌘R</ContextMenuShortcut>
</ContextMenuItem>
</ContextMenuGroup>
</ContextMenuSubContent>
</ContextMenuSub>
<ContextMenuSub>
<ContextMenuSubTrigger>{t.moreTools}</ContextMenuSubTrigger>
<ContextMenuSubContent className="w-44" data-lang={dir === "rtl" ? language : undefined}>
<ContextMenuGroup>
<ContextMenuItem>{t.savePage}</ContextMenuItem>
<ContextMenuItem>{t.createShortcut}</ContextMenuItem>
<ContextMenuItem>{t.nameWindow}</ContextMenuItem>
</ContextMenuGroup>
<ContextMenuSeparator />
<ContextMenuGroup>
<ContextMenuItem>{t.developerTools}</ContextMenuItem>
</ContextMenuGroup>
<ContextMenuSeparator />
<ContextMenuGroup>
<ContextMenuItem variant="destructive">{t.delete}</ContextMenuItem>
</ContextMenuGroup>
</ContextMenuSubContent>
</ContextMenuSub>
</ContextMenuGroup>
<ContextMenuSeparator />
<ContextMenuGroup>
<ContextMenuCheckboxItem checked>{t.showBookmarks}</ContextMenuCheckboxItem>
<ContextMenuCheckboxItem>{t.showFullUrls}</ContextMenuCheckboxItem>
</ContextMenuGroup>
<ContextMenuSeparator />
<ContextMenuGroup>
<ContextMenuRadioGroup value={people} onValueChange={setPeople}>
<ContextMenuLabel>{t.people}</ContextMenuLabel>
<ContextMenuRadioItem value="pedro">{t.pedro}</ContextMenuRadioItem>
<ContextMenuRadioItem value="colm">{t.colm}</ContextMenuRadioItem>
</ContextMenuRadioGroup>
</ContextMenuGroup>
</ContextMenuContent>
</ContextMenu>
);
}
Shortcuts
#Add ContextMenuShortcut to show keyboard hints.
import {
ContextMenu,
ContextMenuContent,
ContextMenuGroup,
ContextMenuItem,
ContextMenuSeparator,
ContextMenuShortcut,
ContextMenuTrigger,
} from "@codefast/ui/context-menu";
export function ContextMenuShortcuts() {
return (
<ContextMenu>
<ContextMenuTrigger className="flex aspect-video w-full max-w-xs items-center justify-center rounded-xl border border-dashed text-sm">
<span className="hidden pointer-fine:inline-block">Right click here</span>
<span className="hidden pointer-coarse:inline-block">Long press here</span>
</ContextMenuTrigger>
<ContextMenuContent>
<ContextMenuGroup>
<ContextMenuItem>
Back
<ContextMenuShortcut>⌘[</ContextMenuShortcut>
</ContextMenuItem>
<ContextMenuItem disabled>
Forward
<ContextMenuShortcut>⌘]</ContextMenuShortcut>
</ContextMenuItem>
<ContextMenuItem>
Reload
<ContextMenuShortcut>⌘R</ContextMenuShortcut>
</ContextMenuItem>
</ContextMenuGroup>
<ContextMenuSeparator />
<ContextMenuGroup>
<ContextMenuItem>
Save
<ContextMenuShortcut>⌘S</ContextMenuShortcut>
</ContextMenuItem>
<ContextMenuItem>
Save As...
<ContextMenuShortcut>⇧⌘S</ContextMenuShortcut>
</ContextMenuItem>
</ContextMenuGroup>
</ContextMenuContent>
</ContextMenu>
);
}
Sides
#Control submenu placement with side and align props.
import {
ContextMenu,
ContextMenuContent,
ContextMenuGroup,
ContextMenuItem,
ContextMenuTrigger,
} from "@codefast/ui/context-menu";
export function ContextMenuSides() {
return (
<div className="grid w-full max-w-sm grid-cols-2 gap-4">
<ContextMenu>
<ContextMenuTrigger className="flex aspect-video w-full max-w-xs items-center justify-center rounded-xl border border-dashed text-sm">
<span className="hidden pointer-fine:inline-block">Right click (top)</span>
<span className="hidden pointer-coarse:inline-block">Long press (top)</span>
</ContextMenuTrigger>
<ContextMenuContent>
<ContextMenuGroup>
<ContextMenuItem>Back</ContextMenuItem>
<ContextMenuItem>Forward</ContextMenuItem>
<ContextMenuItem>Reload</ContextMenuItem>
</ContextMenuGroup>
</ContextMenuContent>
</ContextMenu>
<ContextMenu>
<ContextMenuTrigger className="flex aspect-video w-full max-w-xs items-center justify-center rounded-xl border border-dashed text-sm">
<span className="hidden pointer-fine:inline-block">Right click (right)</span>
<span className="hidden pointer-coarse:inline-block">Long press (right)</span>
</ContextMenuTrigger>
<ContextMenuContent>
<ContextMenuGroup>
<ContextMenuItem>Back</ContextMenuItem>
<ContextMenuItem>Forward</ContextMenuItem>
<ContextMenuItem>Reload</ContextMenuItem>
</ContextMenuGroup>
</ContextMenuContent>
</ContextMenu>
<ContextMenu>
<ContextMenuTrigger className="flex aspect-video w-full max-w-xs items-center justify-center rounded-xl border border-dashed text-sm">
<span className="hidden pointer-fine:inline-block">Right click (bottom)</span>
<span className="hidden pointer-coarse:inline-block">Long press (bottom)</span>
</ContextMenuTrigger>
<ContextMenuContent>
<ContextMenuGroup>
<ContextMenuItem>Back</ContextMenuItem>
<ContextMenuItem>Forward</ContextMenuItem>
<ContextMenuItem>Reload</ContextMenuItem>
</ContextMenuGroup>
</ContextMenuContent>
</ContextMenu>
<ContextMenu>
<ContextMenuTrigger className="flex aspect-video w-full max-w-xs items-center justify-center rounded-xl border border-dashed text-sm">
<span className="hidden pointer-fine:inline-block">Right click (left)</span>
<span className="hidden pointer-coarse:inline-block">Long press (left)</span>
</ContextMenuTrigger>
<ContextMenuContent>
<ContextMenuGroup>
<ContextMenuItem>Back</ContextMenuItem>
<ContextMenuItem>Forward</ContextMenuItem>
<ContextMenuItem>Reload</ContextMenuItem>
</ContextMenuGroup>
</ContextMenuContent>
</ContextMenu>
</div>
);
}
Submenu
#Use ContextMenuSub to nest secondary actions.
import {
ContextMenu,
ContextMenuContent,
ContextMenuGroup,
ContextMenuItem,
ContextMenuSeparator,
ContextMenuShortcut,
ContextMenuSub,
ContextMenuSubContent,
ContextMenuSubTrigger,
ContextMenuTrigger,
} from "@codefast/ui/context-menu";
export function ContextMenuSubmenu() {
return (
<ContextMenu>
<ContextMenuTrigger className="flex aspect-video w-full max-w-xs items-center justify-center rounded-xl border border-dashed text-sm">
<span className="hidden pointer-fine:inline-block">Right click here</span>
<span className="hidden pointer-coarse:inline-block">Long press here</span>
</ContextMenuTrigger>
<ContextMenuContent>
<ContextMenuGroup>
<ContextMenuItem>
Copy
<ContextMenuShortcut>⌘C</ContextMenuShortcut>
</ContextMenuItem>
<ContextMenuItem>
Cut
<ContextMenuShortcut>⌘X</ContextMenuShortcut>
</ContextMenuItem>
</ContextMenuGroup>
<ContextMenuSub>
<ContextMenuSubTrigger>More Tools</ContextMenuSubTrigger>
<ContextMenuSubContent>
<ContextMenuGroup>
<ContextMenuItem>Save Page...</ContextMenuItem>
<ContextMenuItem>Create Shortcut...</ContextMenuItem>
<ContextMenuItem>Name Window...</ContextMenuItem>
</ContextMenuGroup>
<ContextMenuSeparator />
<ContextMenuGroup>
<ContextMenuItem>Developer Tools</ContextMenuItem>
</ContextMenuGroup>
<ContextMenuSeparator />
<ContextMenuGroup>
<ContextMenuItem variant="destructive">Delete</ContextMenuItem>
</ContextMenuGroup>
</ContextMenuSubContent>
</ContextMenuSub>
</ContextMenuContent>
</ContextMenu>
);
}
Anatomy
#How the parts nest — every slot the component exposes, in composition order.
Features
#- Opens via the context-menu key or long-press too, not just right-click.
- variant="destructive" tints text/icon and the focus background for irreversible actions; inset aligns text with icon-led siblings.
- ContextMenuSub/SubTrigger/SubContent nest a secondary menu; side/align on ContextMenuSubContent control submenu placement.
- ContextMenuCheckboxItem and ContextMenuRadioGroup/RadioItem add toggleable or exclusive-choice items.
API reference
#Props for each part of the component. All native element props are also forwarded.
ContextMenuTrigger
The region that opens the menu on right-click (or long-press).
childrenReactNodeThe area users right-click. Style it however you like.
ContextMenuCheckboxItem
A menu item with a checkmark you control.
checkedboolean | "indeterminate"Its controlled toggled state.
onCheckedChange(checked: boolean) => voidCalled when its toggled state changes.
ContextMenuRadioGroup
Single-choice section inside the menu.
valuestringThe controlled selected RadioItem value.
onValueChange(value: string) => voidCalled when the selected RadioItem value changes.
ContextMenuSub
childrenReactNodePair ContextMenuSubTrigger with ContextMenuSubContent for a submenu.
Accessibility
#Built to be keyboard-navigable and screen-reader friendly out of the box.
| Key | Function |
|---|---|
| Arrow+Down | Moves to the next item (once open). |
| Arrow+Right | Opens the focused submenu. |
| Enter | Activates the focused item. |
| Esc | Closes the menu. |
- Opens with the context-menu key or long-press, not just right-click.
- Checkbox and radio items expose aria-checked for assistive tech.
- Always offer the same actions elsewhere — a context menu must not be the only path.
Guidelines
#Conventions that keep usage consistent across an app.
Do
- Use for contextual shortcuts on an element (canvas, row, file).
- Mirror the most-used items in a visible toolbar or button menu.
Don’t
- Don’t hide primary actions only behind right-click.
- Don’t nest submenus more than one level deep.
Related
#Related components
Explore further
Ready to integrate?
Follow the Getting Started guide to install @codefast/ui, or browse the full component gallery.