Context Menu

The actions menu that opens where you right-click. Built on Radix ContextMenu for keyboard navigation and a11y; it shares its surface and density with Dropdown Menu, but anchors to the cursor.

Q3 Marketing Plan
Edited 2 hours ago · 14 files

Right-click to open the menu

Installation#

# One-time setup (tokens + lib helpers)npx koalaui-cli@latest init# Add this component (its dependencies come along)npx koalaui-cli@latest add context-menu

Usage#

usage.tsxTSX
import {  ContextMenu, ContextMenuTrigger, ContextMenuContent,  ContextMenuItem, ContextMenuSeparator, ContextMenuShortcut,} from "@/components/ui/context-menu"import { Card } from "@/components/ui/card"import { Copy, PencilSimple, Trash } from "koala-icons"export function Example() {  return (    <ContextMenu>      <ContextMenuTrigger asChild>        <Card className="w-72 select-none p-6">Right-click me</Card>      </ContextMenuTrigger>      <ContextMenuContent className="w-48">        <ContextMenuItem>          <PencilSimple /> Rename          <ContextMenuShortcut>⌘R</ContextMenuShortcut>        </ContextMenuItem>        <ContextMenuItem>          <Copy /> Duplicate          <ContextMenuShortcut>⌘D</ContextMenuShortcut>        </ContextMenuItem>        <ContextMenuSeparator />        <ContextMenuItem variant="destructive">          <Trash /> Delete        </ContextMenuItem>      </ContextMenuContent>    </ContextMenu>  )}

Anatomy#

import {  ContextMenu,  ContextMenuTrigger,  ContextMenuContent,  ContextMenuLabel,  ContextMenuGroup,  ContextMenuItem,  ContextMenuShortcut,  ContextMenuCheckboxItem,  ContextMenuRadioGroup,  ContextMenuRadioItem,  ContextMenuSeparator,  ContextMenuSub,  ContextMenuSubTrigger,  ContextMenuSubContent,} from "@/components/ui/context-menu"<ContextMenu>  <ContextMenuTrigger />  <ContextMenuContent>    <ContextMenuLabel />    <ContextMenuGroup>      <ContextMenuItem>        <ContextMenuShortcut />      </ContextMenuItem>    </ContextMenuGroup>    <ContextMenuCheckboxItem />    <ContextMenuRadioGroup>      <ContextMenuRadioItem />    </ContextMenuRadioGroup>    <ContextMenuSeparator />    <ContextMenuSub>      <ContextMenuSubTrigger />      <ContextMenuSubContent />    </ContextMenuSub>  </ContextMenuContent></ContextMenu>

Any surface as the trigger#

Wrap any element with ContextMenuTrigger and asChild; the menu opens at the cursor anywhere inside it. An image is the classic right-click target.

Mountain landscape at dusk
Right-click the image

Checkbox items#

Toggle independent options straight from the menu. ContextMenuCheckboxItem closes the menu when toggled, like any row; pass closeOnSelect={false} to flip several in one pass.

Right-click the canvas

Switch items#

Pass variant="switch" to a checkbox item for an option that applies the moment it flips. The menu stays open when it is toggled, so a few can be set before it closes; the row is still a menu checkbox, with the switch as its drawing.

Right-click the canvas

Radio items#

A mutually exclusive choice lives in a ContextMenuRadioGroup with a shared value; the active row shows a check.

Right-click to sort

Groups, submenus & shortcuts#

Section items with ContextMenuGroup, nest a ContextMenuSub, and add right-aligned hints with ContextMenuShortcut (decorative; wire the real binding in your app). Use inset to align items that have no leading icon.

Right-click the editor

API reference#

ContextMenu

The root, Radix ContextMenu.Root; renders no DOM. Every part that renders an element accepts className, merged last.

PropTypeDefaultDescription
onOpenChange(open: boolean) => void-Called when the menu opens or closes. A context menu has no controlled open: the gesture opens it.
modalbooleantrueBlock outside pointer interaction and hide the rest of the page from screen readers while open.
dir"ltr" | "rtl"-Reading direction for keyboard navigation and submenu placement.

ContextMenuTrigger

The area that opens the menu on right-click or long-press, Radix ContextMenu.Trigger. Renders a <span> and forwards all its props.

PropTypeDefaultDescription
asChildbooleanfalseMerge onto your own element (a Card, a figure, a row) instead of rendering a <span>.
disabledbooleanfalseLet the native browser context menu open instead.
Data attributeDescription
data-state"open" or "closed".
data-disabledPresent when disabled.

ContextMenuContent

The menu, Radix ContextMenu.Content portalled to the body and anchored to the cursor; forwards all its props. Declares --surface, so a nested control blends with the menu.

PropTypeDefaultDescription
density"comfortable" | "compact""compact"Row height for every row inside, 36px or 32px. Unset, the rows follow the nearest DensityProvider.
updatePositionStrategy"optimized" | "always""always"always re-anchors an open menu to each new right-click point; optimized only re-measures on scroll and resize.
collisionPaddingnumber8Distance in pixels kept from the viewport edge; also shrinks the width the menu is capped at.
alignOffsetnumber0Offset in pixels from the cursor along the alignment axis.
avoidCollisionsbooleantrueFlip and shift to stay on screen.
loopbooleanfalseWrap keyboard focus from the last row to the first.
onCloseAutoFocus(event: Event) => void-Called as focus returns on close; preventDefault() to keep it elsewhere.
onEscapeKeyDown(event: KeyboardEvent) => void-Called on Escape; preventDefault() to keep the menu open.
Data attributeDescription
data-state"open" or "closed".
data-sideThe side of the cursor the menu opened on, after collisions.
data-align"start", "center" or "end".
CSS variableDescription
--radix-popper-transform-originThe cursor-side origin the menu grows from.
--radix-context-menu-content-available-widthWidth left beside the cursor; the menu's max-width.
--radix-context-menu-content-available-heightHeight left between the cursor and the viewport edge.
--surfaceSet to var(--popover), read by nested Inputs and Selects.

ContextMenuItem

One action row, Radix ContextMenu.Item; forwards all its props. A leading Koala icon is muted and sized to 20px.

PropTypeDefaultDescription
variant"default" | "destructive""default"destructive colours the label, icon and highlight red.
insetbooleanfalseIndent the label to line up with siblings that lead with an icon.
density"comfortable" | "compact""compact"Row height, 36px or 32px. Falls back to the menu's density, then the nearest DensityProvider.
disabledbooleanfalseSkip the row in keyboard navigation and ignore clicks.
onSelect(event: Event) => void-Called when the row is chosen; preventDefault() to keep the menu open.
textValuestring-Text used for typeahead when the row's content is not plain text.
asChildbooleanfalseMerge onto your own element, such as a link.
Data attributeDescription
data-highlightedPresent while the row has keyboard or pointer focus.
data-disabledPresent when the row is disabled.
data-insetPresent when inset is set.

ContextMenuCheckboxItem

A toggle row, Radix ContextMenu.CheckboxItem (menuitemcheckbox); forwards all its props. Shows an ink check on the right, or a mini Switch.

PropTypeDefaultDescription
checkedboolean | "indeterminate"-The checked state.
onCheckedChange(checked: boolean) => void-Called when the row is toggled.
variant"check" | "switch""check"How the state reads: the check, or a Switch drawing for a setting that applies the moment it flips.
closeOnSelectbooleantrueClose the menu when the row is toggled. Defaults to false for variant="switch".
density"comfortable" | "compact""compact"Row height, 36px or 32px. Falls back to the menu's density, then the nearest DensityProvider.
disabledbooleanfalseSkip the row and ignore clicks.
onSelect(event: Event) => void-Called when the row is chosen.
Data attributeDescription
data-state"checked", "unchecked" or "indeterminate".
data-highlightedPresent while the row has keyboard or pointer focus.
data-disabledPresent when the row is disabled.

ContextMenuRadioGroup

Holds one exclusive choice, Radix ContextMenu.RadioGroup. Forwards all native <div> props.

PropTypeDefaultDescription
valuestring-The selected item's value.
onValueChange(value: string) => void-Called when another item is chosen.

ContextMenuRadioItem

One option in a radio group, Radix ContextMenu.RadioItem (menuitemradio); the selected row shows the same ink check as a checkbox row.

PropTypeDefaultDescription
value*string-The value this row selects.
density"comfortable" | "compact""compact"Row height, 36px or 32px. Falls back to the menu's density, then the nearest DensityProvider.
disabledbooleanfalseSkip the row and ignore clicks.
onSelect(event: Event) => void-Called when the row is chosen; preventDefault() to keep the menu open.
Data attributeDescription
data-state"checked" or "unchecked".
data-highlightedPresent while the row has keyboard or pointer focus.
data-disabledPresent when the row is disabled.

ContextMenuLabel

A small muted section heading, Radix ContextMenu.Label; not focusable. Forwards all native <div> props.

PropTypeDefaultDescription
insetbooleanfalseIndent to line up with rows that lead with an icon.
density"comfortable" | "compact""compact"Row height, 36px or 32px. Falls back to the menu's density, then the nearest DensityProvider.

ContextMenuSeparator

A hairline between sections, Radix ContextMenu.Separator, stopping at the menu's ring. Forwards all native <div> props.

PropTypeDefaultDescription
density"comfortable" | "compact""compact"Row height, 36px or 32px. Falls back to the menu's density, then the nearest DensityProvider.

ContextMenuGroup

Groups related rows for assistive tech, Radix ContextMenu.Group; unstyled. Forwards all native <div> props.

ContextMenuShortcut

A right-aligned shortcut hint, Koala's Kbd at size="sm"; decorative, it binds nothing. Forwards all Kbd props.

ContextMenuSub

The root of a nested menu, Radix ContextMenu.Sub; renders no DOM.

PropTypeDefaultDescription
openboolean-Controlled open state.
defaultOpenbooleanfalseInitial open state when uncontrolled.
onOpenChange(open: boolean) => void-Called when the submenu opens or closes.

ContextMenuSubTrigger

The row that opens a submenu, Radix ContextMenu.SubTrigger, with a caret on the right; forwards all its props.

PropTypeDefaultDescription
insetbooleanfalseIndent to line up with rows that lead with an icon.
density"comfortable" | "compact""compact"Row height, 36px or 32px. Falls back to the menu's density, then the nearest DensityProvider.
disabledbooleanfalseSkip the row and keep the submenu closed.
textValuestring-Text used for typeahead when the row's content is not plain text.
Data attributeDescription
data-state"open" or "closed".
data-highlightedPresent while the row has keyboard or pointer focus.
data-disabledPresent when the row is disabled.

ContextMenuSubContent

The nested menu, Radix ContextMenu.SubContent; same surface as the content, and it animates out on close. Forwards all its props.

PropTypeDefaultDescription
density"comfortable" | "compact""compact"Row height, 36px or 32px. Falls back to the menu's density, then the nearest DensityProvider.
sideOffsetnumber0Gap in pixels between the trigger row and the submenu.
alignOffsetnumber0Offset in pixels along the alignment axis.
collisionPaddingnumber0Distance in pixels kept from the viewport edge.
loopbooleanfalseWrap keyboard focus from the last row to the first.
Data attributeDescription
data-state"open" or "closed".
data-sideThe side the submenu opened on, after collisions.
data-align"start", "center" or "end".
CSS variableDescription
--radix-popper-transform-originThe trigger-side origin the submenu grows from.

ContextMenuPortal

Radix ContextMenu.Portal. ContextMenuContent already portals itself, so you rarely need it.

PropTypeDefaultDescription
containerHTMLElement | nulldocument.bodyWhere to portal the content.

FAQ#

They share the same surface and items; the difference is how they open. A Dropdown Menu opens on click of a visible trigger button, so the affordance is obvious. A Context Menu opens on right-click (or long-press) of an arbitrary area like a card, row, image, or canvas, so it is best for secondary or power-user actions that should not take up button space. Many apps offer both: a more-actions button and the same actions on right-click.