Context Menu
The right-click menu — actions attached to a thing rather than to a button.
Installation
npx shadcn@latest add https://qure-ui.qure.ai/r/context-menu.jsonUsage
import {
ContextMenu, ContextMenuContent, ContextMenuItem, ContextMenuTrigger,
} from '@/components/ui/context-menu'<ContextMenu>
<ContextMenuTrigger className="rounded-lg border p-4">
ACC-4471902 — CT Thorax
</ContextMenuTrigger>
<ContextMenuContent>
<ContextMenuItem>Open report</ContextMenuItem>
<ContextMenuItem>Compare with prior</ContextMenuItem>
</ContextMenuContent>
</ContextMenu>A context menu is an accelerator, never the only route. Right-click is invisible, absent on
touch until a long press is discovered, and awkward for anyone driving by keyboard. Every action
in here should also exist somewhere a person can see — usually the same list behind a … button.
Composition
<ContextMenu> {/* owns the open state and the pointer position */}
<ContextMenuTrigger> {/* the area that responds to right-click */}
<ContextMenuContent> {/* portal + positioner + popup */}
<ContextMenuLabel> {/* names what the menu is acting on */}
<ContextMenuItem> {/* an action */}
<ContextMenuItemText> {/* label column, when there is a description */}
<ContextMenuDescription> {/* the second line */}
<ContextMenuShortcut> {/* the key hint */}
<ContextMenuSeparator>
<ContextMenuCheckboxItem> {/* a toggle; keeps the menu open */}
<ContextMenuRadioGroup> {/* one-of-many, with ContextMenuRadioItem */}
<ContextMenuSub> {/* a nested menu */}
<ContextMenuSubTrigger>
<ContextMenuSubContent>The trigger renders a <div>, not a button, because the target is usually a region — a row, a
viewport, a thumbnail. That has a consequence worth stating: a <div> is not in the tab order,
so if the menu is the only way to reach an action, the region needs a focusable control inside
it as well.
ContextMenuLabel throws unless it is inside a ContextMenuGroup or a
ContextMenuRadioGroup — "MenuGroupContext is missing", at runtime rather than at compile
time. A heading in a menu names a group, so the items it names go inside the group with it.
Everything below the trigger is Base UI's Menu, so the parts, the state attributes and the
keyboard behaviour are identical to DropdownMenu. The difference is entirely in what opens it:
a right-click or a long press, anchored to the pointer rather than to an element.
On a worklist row
Each row is its own ContextMenu, so the menu can name the study it was opened on. A menu that
says "Open ACC-4471902" is checkable at a glance; one that says "Open" leaves the reader hoping
they right-clicked the row they meant.
In the viewer
The viewport is where right-click pays for itself: the pointer is already over the image, and overlay and window presets are exactly the settings you want without crossing the screen to a toolbar. Checkbox and radio items keep the menu open, so several can be set in one pass.
Submenus
One level of nesting, for lists that are genuinely lists — the priors for this patient, the teams a study can be routed to.
Where it opens
The menu is anchored to the pointer, so side and align are measured from the click, not from
the trigger. The defaults — side="right", align="start", a 4px alignOffset — put the top
left corner just below and right of the cursor, and it flips near an edge.
The trigger sets user-select: none. Without it, a right-click drag selects the accession
number underneath the menu, and the selection is still sitting there after the menu closes.
Keyboard
There is a keyboard route in: the Menu key, or Shift+F10, opens the menu on the focused element. Once open it behaves like any other menu — arrows to move, Enter to act, Right and Left for submenus, Escape to close.
API Reference
Everything Base UI's Context Menu accepts.
What we add on ContextMenuItem:
Prop
Type
On ContextMenuContent:
Prop
Type
ContextMenuRoot takes the Menu root's props minus the ones a context menu cannot have —
modal, handle, triggerId, openOnHover and the hover delays. There is no trigger element
to hover, and the pointer position replaces the trigger id.