Morphos
Overlays

Context Menu

A menu that appears at the cursor position on right-click, offering context-specific actions.

Interactive example

Installation

npm install @morphos/overlays
pnpm add @morphos/overlays
yarn add @morphos/overlays
bun add @morphos/overlays

Import

import {
  ContextMenu,
  ContextMenuTrigger,
  ContextMenuContent,
  ContextMenuItem,
} from '@morphos/overlays'

Usage

@Component()
class MyComponent extends StatefulComponent {
  @State() contextMenu = new ContextMenu()

  render() {
    return (
      <>
        <ContextMenuTrigger contextMenu={this.contextMenu}>
          Right-click anywhere in this area
        </ContextMenuTrigger>
        <ContextMenuContent contextMenu={this.contextMenu}>
          <ContextMenuItem contextMenu={this.contextMenu} label="Copy" value="copy" onSelect={() => console.log('copy')} />
          <ContextMenuItem contextMenu={this.contextMenu} label="Paste" value="paste" onSelect={() => console.log('paste')} />
          <ContextMenuItem contextMenu={this.contextMenu} label="Delete" value="delete" disabled />
        </ContextMenuContent>
      </>
    )
  }
}

Compound components

ComponentDescription
ContextMenuRoot state manager. Tracks open state and the cursor coordinates captured on contextmenu.
ContextMenuTrigger<div> that listens for the native contextmenu event, prevents the default browser menu, and opens the menu at the cursor. Sets data-open.
ContextMenuContent<ul role="menu"> rendered into a Portal, positioned with position: fixed at the captured coordinates. Closes on outside click or Escape.
ContextMenuItem<li role="menuitem">. Selectable via click or Enter/Space.

Props — ContextMenu

PropTypeDefaultDescription
onOpenChange(open: boolean) => voidCalled when the menu opens or closes.
childrenChildrenContent — typically ContextMenuTrigger and ContextMenuContent.

Methods

MethodDescription
open()Opens the context menu. Emits onOpenChange(true).
close()Closes the context menu. Emits onOpenChange(false).
setPosition(x, y)Sets the cursor coordinates used to position the menu. Called internally by ContextMenuTrigger's contextmenu handler.

Unlike the other overlays in this package, ContextMenu has no open / defaultOpen props — it is always uncontrolled, since it is driven entirely by the browser's native contextmenu event rather than a click on a fixed trigger element.

Props — ContextMenuTrigger

PropTypeDefaultDescription
contextMenuContextMenuThe ContextMenu instance this trigger controls.
childrenChildrenThe trigger area content.
classstringCSS class.
idstringHTML id.

Props — ContextMenuContent

PropTypeDefaultDescription
contextMenuContextMenuThe ContextMenu instance this content belongs to.
childrenChildrenContextMenuItem elements.
classstringCSS class.
idstringHTML id.
aria-labelstringAccessible label for the menu.

Props — ContextMenuItem

PropTypeDefaultDescription
contextMenuContextMenuThe ContextMenu instance this item belongs to.
valuestringUnique value identifier.
labelstringDisplay text, used when no children are passed.
disabledbooleanfalseDisables the item.
onSelect() => voidCalled when the item is selected.
childrenChildrenItem content. Falls back to label if omitted.
classstringCSS class.
idstringHTML id.

Positioning

ContextMenuContent is rendered with position: fixed at the coordinates captured on the most recent contextmenu event, read from the internal _x / _y state on the ContextMenu instance. Unlike Dropdown, Popover, Tooltip, and PreviewCard, ContextMenu does not use computeAnchorPosition() from @morphos/core — there is no anchor element to measure, only a cursor position.

data-* attributes

AttributeElementWhen present
data-openContextMenuTriggerMenu is open
data-openContextMenuContent rootMenu is open
data-disabledContextMenuItemdisabled is true

ContextMenuContent renders with role="menu" and each ContextMenuItem renders with role="menuitem". The menu closes automatically when the user clicks outside, presses Escape, or selects an item.

Styling example

.morphos-context-menu-content {
  z-index: 100;
  min-width: 10rem;
  padding: var(--morphos-space-1);
  list-style: none;
  border: 1px solid var(--morphos-color-border);
  border-radius: var(--morphos-radius-md);
}

.morphos-context-menu-item[data-disabled] {
  opacity: 0.5;
  cursor: not-allowed;
}

On this page