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: '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)

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<[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 booleanfalseSkip pointer interaction.

Custom menu descendants

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

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.