Skip to content

ContextMenu

Terminal window
$ npx shadcn@latest add https://lacodda.github.io/dowel/r/context-menu.json
See it liveRight click inside the panel to open it.→

A list of actions opened by a right click, or by a long press on a touch screen, over an area rather than from a button. The trigger is not a control

  • it is the region the menu belongs to: a row, a canvas, a file tile - so it renders a <div> and is announced as nothing at all.

That is the only difference from Menu. Everything below the root is Menu’s own - Base UI re-exports the portal, the positioner, the popup and the items from the menu package - so the popup that opens here is the same popup, with the same keyboard, the same type-ahead and the same submenus. The clothes are imported from Menu rather than copied, which is declared in the component’s dependency budget: two class lists that started identical do not stay that way.

Use it when the actions belong to a thing on the screen and there is no room for a button beside it - a row in a long table, an item on a canvas. Use Menu instead when there is a button, because a right click is undiscoverable: nobody finds a context menu they were not expecting. A context menu should repeat actions that are reachable some other way, not hide them.

The keyboard opens it from inside the area. Shift+F10 and the Menu key fire the same contextmenu event a right click does, on whatever has focus - so the menu opens for a focused row, a focused tile, a focused button inside the area, and Escape puts the focus back there. An area with nothing in it that can take focus is a menu only a pointer can open: wrap things a reader can tab to.

It positions against the point that was clicked, so there is no side or align to give it, and no anchor to attach it to from elsewhere. Base UI also withholds openOnHover, modal and handle here for the same reason.

import {
ContextMenu,
ContextMenuItem,
ContextMenuPopup,
ContextMenuSeparator,
ContextMenuTrigger,
} from '@/components/ui/context-menu'
<ContextMenu>
<ContextMenuTrigger render={<tr />}>{row}</ContextMenuTrigger>
<ContextMenuPopup>
<ContextMenuItem onClick={rename}>{t('rename')}</ContextMenuItem>
<ContextMenuItem onClick={duplicate}>{t('duplicate')}</ContextMenuItem>
<ContextMenuSeparator />
<ContextMenuItem tone="danger" onClick={remove}>
{t('delete')}
</ContextMenuItem>
</ContextMenuPopup>
</ContextMenu>

Props

Read from the types in ui/context-menu.tsx, the file a product copies - so the description of each prop is also what an editor shows over it there.

ContextMenu

The root. It positions against the pointer, so there is nothing to anchor and nothing to control but onOpenChange.

Base UI's ContextMenu.Root, unchanged under dowel's name - its props are on its own page.

ContextMenuTrigger

The area that opens it. A <div>, not a button: give it render to make it the row or the canvas it belongs to rather than a wrapper.

Base UI's ContextMenu.Trigger, unchanged under dowel's name - its props are on its own page.

ContextMenuGroup

A labelled group of items, for a menu long enough to need headings.

Base UI's ContextMenu.Group, unchanged under dowel's name - its props are on its own page.

ContextMenuSub

A submenu, opened from a ContextMenuSubTrigger inside the parent.

Base UI's ContextMenu.SubmenuRoot, unchanged under dowel's name - its props are on its own page.

ContextMenuPopup

The panel. Portalled, and positioned against the point that was clicked rather than against an element.

PropTypeDefault
sizesm | md | lgHow wide the popup starts. The same three as Menu's.
containerHTMLElement | ShadowRoot | RefObject<HTMLElement | ShadowRoot | null> | nullWhere to portal to. Defaults to the raised host of the overlay this is opened inside (layer.tsx), and to the document body when there is none - either way not the element it was opened from, whose overflow would clip it. Pass an element to put it somewhere else, such as a container being screenshotted.

And every prop of Base UI's ContextMenu.Popup, passed on.

ContextMenuItem

An action.

PropTypeDefault
tonedefault | dangerdanger draws the destructive one apart, in the colour of something that cannot be undone.

And every prop of Base UI's ContextMenu.Item, passed on.

ContextMenuSubTrigger

An item that opens a submenu. Drawn like any other, because it is one.

PropTypeDefault
tonedefault | dangerdanger draws the destructive one apart, in the colour of something that cannot be undone.

And every prop of Base UI's ContextMenu.Item, passed on.

ContextMenuCheckboxItem

An item that carries a tick. The state is the caller's.

PropTypeDefault
tonedefault | dangerdanger draws the destructive one apart.

And every prop of Base UI's ContextMenu.CheckboxItem, passed on.

ContextMenuCheckboxIndicator

The tick itself, drawn only when the item is checked.

Base UI's ContextMenu.CheckboxItemIndicator, unchanged under dowel's name - its props are on its own page.

ContextMenuSeparator

A line between groups of items. Decorative, and marked as such.

Every prop of Base UI's ContextMenu.Separator, passed on.

ContextMenuGroupLabel

The caption above a group.

Every prop of Base UI's ContextMenu.GroupLabel, passed on.

Accessibility

The examples on the stand, through axe 4.14.0 in a browser, in both themes, against WCAG 2.2 A and AA and axe's best practices: 6 rules applied and passed, and none failed. The accessibility report has every item.

History

Each release that changed ui/context-menu.tsx - the file a product copies - newest first.

  1. v0.40.0
    • fix let ContextMenuCheckboxItem take checked and onCheckedChange 290b4dc
  2. v0.33.0
    • feat one caption utility and the compact rail width 88a3fee
  3. v0.32.0breaking
    • feat a layer for popups inside overlays, a body that scrolls alone 5244cfd
  4. v0.9.0
    • feat Menu, ContextMenu, Select and Combobox 243cc12