Context Menu — <mv-context-menu>

Context menu opened at the pointer (right-click), on touch long-press or from the keyboard (Shift+F10 / Menu key); reuses the Menu engine and styles, including the data-motion="glide" animation.

CategoryOverlays
TypeWeb Component (<mv-context-menu>)
Statusstable
Also installsmenu, popover
Keywordscontext-menu, right-click, long-press, menu, submenu

When to use

Avoid when

Install

node scripts/add.mjs context-menu --out ./src/marvelous

AI agent with the Marvelous UI MCP server: install_components({ slugs: ["context-menu"], target_dir: "<absolute path>/src/marvelous", framework: "react" }).

Files copied (dependencies included): tokens/tokens.css, core/base.css, core/dismiss.js, core/dom.js, core/element.js, core/focus.js, core/motion.js, core/position.js, components/context-menu/context-menu.js, components/context-menu/context-menu.css, components/menu/menu.js, components/menu/menu.css, components/popover/popover.js, components/popover/popover.css.

Usage

Canonical markup — start from it and customize with attributes, data-* and CSS variables:

<div id="ctx-demo" style="display:grid;gap:1rem;width:min(26rem,100%)">
  <mv-context-menu>
    <div class="mv-context-menu-area" aria-label="Layer “Hero banner”" style="min-height:11rem">
      <svg viewBox="0 0 24 24" width="28" height="28" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true" style="opacity:.6"><path d="m12 3 9 5-9 5-9-5z"/><path d="m3 13 9 5 9-5"/></svg>
      <strong style="color:var(--mv-fg);font-weight:500">Right-click the layer</strong>
      <span style="font-size:.75rem">Long-press on mobile · Shift + F10 on keyboard</span>
    </div>
    <div data-content style="--mv-menu-width:15rem">
      <button role="menuitem" value="copy">Copy <span class="mv-menu-shortcut">⌘C</span></button>
      <button role="menuitem" value="paste" disabled>Paste <span class="mv-menu-shortcut">⌘V</span></button>
      <button role="menuitem" value="duplicate">Duplicate <span class="mv-menu-shortcut">⌘D</span></button>
      <div data-submenu>
        <button role="menuitem">Arrange</button>
        <div data-content style="--mv-menu-width:13rem">
          <button role="menuitem" value="bring-to-front">Bring to front <span class="mv-menu-shortcut">⇧⌘]</span></button>
          <button role="menuitem" value="bring-forward">Bring forward <span class="mv-menu-shortcut">⌘]</span></button>
          <button role="menuitem" value="send-backward">Send backward <span class="mv-menu-shortcut">⌘[</span></button>
          <button role="menuitem" value="send-to-back">Send to back <span class="mv-menu-shortcut">⇧⌘[</span></button>
        </div>
      </div>
      <hr>
      <button role="menuitemcheckbox" value="lock">Lock <span class="mv-menu-shortcut">⇧⌘L</span></button>
      <button role="menuitemcheckbox" value="hide">Hide <span class="mv-menu-shortcut">⇧⌘H</span></button>
      <hr>
      <div role="group" data-name="blend">
        <div class="mv-menu-label" data-inset>Blend mode</div>
        <button role="menuitemradio" value="normal" aria-checked="true">Normal</button>
        <button role="menuitemradio" value="multiply">Multiply</button>
        <button role="menuitemradio" value="overlay">Overlay</button>
      </div>
      <hr>
      <button role="menuitem" value="delete" data-variant="destructive">Delete <span class="mv-menu-shortcut">⌫</span></button>
    </div>
  </mv-context-menu>
  <mv-context-menu data-motion="glide">
    <div class="mv-context-menu-area" aria-label="File “q3-report.pdf”" style="min-height:6rem">
      <strong style="color:var(--mv-fg);font-weight:500">data-motion="glide"</strong>
      <span style="font-size:.75rem">The menu blooms from the pointer, the highlight glides</span>
    </div>
    <div data-content style="--mv-menu-width:13rem">
      <button role="menuitem" value="open">Open <span class="mv-menu-shortcut">↵</span></button>
      <button role="menuitem" value="quick-look">Quick Look <span class="mv-menu-shortcut">␣</span></button>
      <div data-submenu>
        <button role="menuitem">Open with</button>
        <div data-content>
          <button role="menuitem" value="app-reader">PDF reader</button>
          <button role="menuitem" value="app-browser">Browser</button>
        </div>
      </div>
      <hr>
      <button role="menuitem" value="rename">Rename</button>
      <button role="menuitem" value="delete" data-variant="destructive">Delete <span class="mv-menu-shortcut">⌫</span></button>
    </div>
  </mv-context-menu>
  <p data-role="out" aria-live="polite" style="margin:0;text-align:center;font-size:.8125rem;color:var(--mv-fg-muted)">No action yet.</p>
</div>
<script type="module">
  const root = document.getElementById("ctx-demo");
  const out = root.querySelector('[data-role="out"]');
  root.addEventListener("mv-select", (e) => {
    const { label, checked, group } = e.detail;
    out.textContent = group ? `Blend: ${label}` : checked === undefined ? `Action: ${label}` : `${label}: ${checked ? "on" : "off"}`;
  });
</script>

API

Attributes

NameTypeDescription
disabledbooleanLets the browser’s native menu through.
data-motion"glide"Same option as mv-menu: the panel blooms from the click point, the highlight glides between items, submenus slide in.

Properties

NameTypeDescription
isOpenbooleanRead-only.
menuMenuPanelInstance of the engine shared with mv-menu.

Methods

NameDescription
openAt(x, y, { focus })Opens at viewport coordinates; focus = "first" | "panel".
hide({ focus })Closes; focus=true returns focus to the element that was active before opening (or to the area).

Events

NameDescription
mv-selectCancelable. detail: { value, label, item, checked?, group? } (same as mv-menu).
mv-opendetail: { x, y }.
mv-closeAfter closing.

Content structure

NameDescription
first childTarget area; made focusable (tabindex=0) if it isn’t. Its aria-label names the menu.
[data-content]Panel: same markup as mv-menu (menuitem, checkbox, radio, [data-submenu], <hr>, .mv-menu-label, .mv-menu-shortcut).

CSS classes

NameDescription
mv-context-menu-areaReady-made dashed area (visual feedback during long-press).

CSS variables

NameDescription
--mv-menu-*All Menu variables apply (width, highlight, background, radius).

Accessibility

role=menu panel (APG “Menu” pattern) with full navigation: ↑↓ Home/End, typeahead, → / ← for submenus, Escape and Tab close. Keyboard opening via Shift+F10 or the Menu key from the focusable area (focus on the first item); focus then returns to the original element.