Context Menu
Right-click context menu overlay with nested submenus, optional horizontal icon groups, shortcuts, boolean toggles, and danger actions.
import { useContextMenuAPI, contextMenu } from 'phoundry-ui';
Programmatic Open
const api = useContextMenuAPI();
const items: MenuItem[] = [
{ type: 'action', label: 'Copy', icon: 'phoundry-mono:copy', id: 'copy', action: () => {} },
{ type: 'separator' },
{ type: 'action', label: 'Delete', danger: true, id: 'del', action: () => {} },
];
api.open(() => items, e.clientX, e.clientY, { ariaLabel: 'Edit actions' });
Right-Click Area (Attachment)
<div {@attach contextMenu({ api, items: menuItems })}>
Right-click here
</div>
OpenMenuOptions (4th argument to api.open)
| Prop | Type | Default | Description |
|---|
| ariaLabel | string | - | Accessible name for the menu panel. Defaults to `"Context menu"`. |
| anchor | 'start' | 'end' | - | Horizontal alignment relative to the open point. |
| side | PopoverSide | - | Which edge of the trigger the menu opens from. Defaults to `'bottom'`. |
| onClose | () => void | - | Called when the menu closes (Escape, backdrop, or after an action). |
| trigger | unknown | - | Optional trigger reference for callers that track menu origin. |
ActionMenuItem
| Prop | Type | Default | Description |
|---|
| type required | 'action' | - | Menu item that triggers a callback. |
| id required | string | - | Stable key for shortcuts / analytics. |
| label required | string | - | Display text. |
| icon | string | - | Iconify icon string. |
| shortcut | string | - | Display-only shortcut hint. |
| pluginId | string | - | Optional plugin scope for shortcut manager lookups (`pluginId.id`). |
| danger | boolean | false | Red danger styling. |
| disabled | boolean | false | Non-interactive row. |
| selected | boolean | - | Decorates the row as checked/active where supported. |
| preventClose | boolean | false | Keep the overlay open after invoking the action. |
| action required | () => void | Promise<void> | - | Handler run when the row is activated. |
BooleanMenuItem
| Prop | Type | Default | Description |
|---|
| type required | 'boolean' | - | Toggle row with checkbox on the right. |
| label required | string | - | Display text. |
| icon | string | - | Iconify icon string. |
| pluginId | string | - | Plugin ID (same role as on action items). |
| preventClose | boolean | false | Keep the menu open after toggling. |
| value required | boolean | - | Checked state. |
| onchange required | (nextValue: boolean) => void | Promise<void> | - | Called with the new value after the user toggles the row. |
SubmenuMenuItem
| Prop | Type | Default | Description |
|---|
| type required | 'submenu' | - | Nested submenu container. |
| label required | string | - | Submenu trigger label. |
| items required | MenuItem[] | - | Child menu items. |
GroupMenuItem
| Prop | Type | Default | Description |
|---|
| type required | 'group' | - | Horizontal row of icon-only controls. |
| id required | string | - | Stable key (see MenuItemBase). |
| items required | GroupMenuChildItem[] | - | Toolbar row: **`action`** and **`boolean`** as icon-only `Button`s (labels via tooltip / `aria-label`); **`separator`** as a vertical rule. |
| disabled | boolean | false | Disables all controls in the group. |
SeparatorMenuItem
| Prop | Type | Default | Description |
|---|
| type required | 'separator' | - | Horizontal rule between groups. |
LabelMenuItem
| Prop | Type | Default | Description |
|---|
| type required | 'label' | - | Non-interactive heading row. |
| label required | string | - | Muted caption text. |
CustomMenuItem
| Prop | Type | Default | Description |
|---|
| type required | 'custom' | - | Fully custom row body via snippet. |
| id required | string | - | Stable key. |
| render required | Snippet<[context?: CustomMenuItemContext]> | - | Snippet rendered inside the menu row. The optional context can open a descendant panel from one of the snippet controls or close the current panel. |
| disabled | boolean | false | Skip pointer interaction. |
The snippet context exposes openSubmenu(item, trigger, intent?) and closePanel(). Use openSubmenu when an editor rendered by a CustomMenuItem needs another menu level, such as an option-level editor. Pass the control element that should anchor the child panel and use intent: 'keyboard' for keyboard activation.
Descendants opened this way stay in the same menu session. Pointer opens preserve neutral focus; keyboard opens focus the first interactive control. Arrow Left or Escape closes the current child panel and returns focus to its trigger. closePanel() closes only the panel containing that custom item (or the root session when called at the root).
provideContextMenu() must be called in the root layout, and ContextMenuOverlay must be rendered there - or call setupOverlays() once to initialize all overlay managers together. - The menu auto-positions to stay within the viewport.
- Arrow keys, Home, and End move the highlighted row; Enter and Space activate it. Escape closes the menu. The active row uses the same background as hover; focus returns to the element that opened the menu.
api.open accepts either a static array or () => MenuItem[] so menus can read live state (as with the attachment demo). Pass ariaLabel in the fourth argument when the default "Context menu" label is too generic. - For click-based dropdowns from a button, use
ButtonDropdown instead.