Context Menu
A menu that opens where the pointer is. Right-click a target, or long-press it on a touch screen. Use it for shortcuts to actions that are also reachable another way, such as a More button or a keyboard shortcut.
Usage
import {
ContextMenu,
ContextMenuContent,
ContextMenuGroup,
ContextMenuItem,
ContextMenuLabel,
ContextMenuSeparator,
ContextMenuShortcut,
ContextMenuTrigger,
} from "@tulipfarm/sprout/context-menu";
<ContextMenu>
<ContextMenuTrigger tabIndex={0} role="group" aria-label="Sample card actions">
Right-click this sample card
</ContextMenuTrigger>
<ContextMenuContent>
<ContextMenuGroup>
<ContextMenuLabel>Example actions</ContextMenuLabel>
<ContextMenuItem onClick={open}>
Open<ContextMenuShortcut>Enter</ContextMenuShortcut>
</ContextMenuItem>
<ContextMenuItem disabled>Unavailable</ContextMenuItem>
</ContextMenuGroup>
<ContextMenuSeparator />
<ContextMenuItem variant="destructive" onClick={remove}>
Delete sample
</ContextMenuItem>
</ContextMenuContent>
</ContextMenu>ContextMenuTrigger renders a div that wraps the target. Pass render to use another element. Give it tabIndex={0}, a role, and an aria-label when people should reach it with the keyboard.
Keyboard access
Right-click and long-press have no keyboard equivalent, so always offer the same actions another way. The items are plain Base UI menu parts, so the same elements work inside DropdownMenuContent. Open that menu with open and onOpenChange when the focused target receives Shift + F10 or the Menu key, and use finalFocus to return focus to the target.
const items = <>{/* ContextMenuItem, ContextMenuSub, … */}</>;
<ContextMenu>
<ContextMenuTrigger
tabIndex={0}
role="group"
aria-label="Sample card actions"
onKeyDown={(event) => {
if ((event.shiftKey && event.key === "F10") || event.key === "ContextMenu") {
event.preventDefault();
returnFocus.current = event.currentTarget;
setOpen(true);
}
}}
>
Right-click this sample card
</ContextMenuTrigger>
<ContextMenuContent>{items}</ContextMenuContent>
</ContextMenu>
<DropdownMenu open={open} onOpenChange={setOpen}>
<DropdownMenuTrigger render={<Button variant="outline" />}>Open sample actions</DropdownMenuTrigger>
<DropdownMenuContent size="menu" finalFocus={() => returnFocus.current ?? true}>
{items}
</DropdownMenuContent>
</DropdownMenu>Variants and states
ContextMenuItem variant is "default" or "destructive"; destructive items use the danger color and a tinted highlight. Items with a leading icon line up with labels and items that set inset. disabled items stay readable at half opacity and can still be reached with arrow keys. While a row syncs, disable the actions that would change it and keep the rest.
File rows
Right-click a row, or use its More button. While Contracts syncs, edits and trash are disabled but links still work.
Checkboxes and radios
ContextMenuCheckboxItem and ContextMenuRadioItem show a check on the right. Checkboxes keep the menu open; pass closeOnClick to close after one choice. Disable the last visible column so the table never goes empty. ContextMenuSub nests a menu: ContextMenuSubTrigger adds a chevron and opens ContextMenuSubContent to the side.
Table header
Right-click the header to choose columns. Shift + F10 opens the same menu from the keyboard.
Row height: Comfortable · Sort: Due date
Accessibility
The popup has role="menu". Arrow keys move, typing jumps to a matching item, Arrow Right opens a submenu and Arrow Left closes it, and Escape closes the menu. A long press opens it on touch screens, and the trigger turns off text selection and the system callout so the press isn’t interrupted. A focused trigger shows a focus ring.
Menus fade and scale in over 120ms. Motion turns off when reduced motion is requested. In forced-colors mode the highlighted item gets an outline.
Theme tokens
Menus use --sprout-surface, --sprout-line, --sprout-hover, --sprout-text, --sprout-muted, --sprout-danger, --sprout-danger-subtle, --sprout-focus, --sprout-shadow-overlay, and the panel and control radii. They need no brand color, so they look the same with the neutral and TulipFarm themes in light and dark mode.