Phoundry UI

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

Show code
const api = useContextMenuAPI();

const items: MenuItem[] = [
  { type: 'action', label: 'Copy', icon: 'carbon: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)

Right-click anywhere in this area
Show code
<div {@attach contextMenu({ api, items: menuItems })}>
  Right-click here
</div>

OpenMenuOptions (4th argument to api.open)

PropTypeDefaultDescription
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

PropTypeDefaultDescription
type required'action'-Menu item that triggers a callback.
id requiredstring-Stable key for shortcuts / analytics.
label requiredstring-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 booleanfalseRed danger styling.
disabled booleanfalseNon-interactive row.
selected boolean-Decorates the row as checked/active where supported.
preventClose booleanfalseKeep the overlay open after invoking the action.
action required() => void | Promise<void>-Handler run when the row is activated.

BooleanMenuItem

PropTypeDefaultDescription
type required'boolean'-Toggle row with checkbox on the right.
label requiredstring-Display text.
icon string-Iconify icon string.
pluginId string-Plugin ID (same role as on action items).
preventClose booleanfalseKeep the menu open after toggling.
value requiredboolean-Checked state.
onchange required(nextValue: boolean) => void | Promise<void>-Called with the new value after the user toggles the row.

SubmenuMenuItem

PropTypeDefaultDescription
type required'submenu'-Nested submenu container.
label requiredstring-Submenu trigger label.
items requiredMenuItem[]-Child menu items.

GroupMenuItem

PropTypeDefaultDescription
type required'group'-Horizontal row of icon-only controls.
id requiredstring-Stable key (see MenuItemBase).
items requiredGroupMenuChildItem[]-Toolbar row: **`action`** and **`boolean`** as icon-only `Button`s (labels via tooltip / `aria-label`); **`separator`** as a vertical rule.
disabled booleanfalseDisables all controls in the group.

SeparatorMenuItem

PropTypeDefaultDescription
type required'separator'-Horizontal rule between groups.

LabelMenuItem

PropTypeDefaultDescription
type required'label'-Non-interactive heading row.
label requiredstring-Muted caption text.

CustomMenuItem

PropTypeDefaultDescription
type required'custom'-Fully custom row body via snippet.
id requiredstring-Stable key.
render requiredSnippet-Snippet rendered inside the menu row.
disabled booleanfalseSkip pointer interaction.

Usage tips

  • 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.