Navigation✨ Premium

Context Menu

Context menu triggered on right-click, positioned exactly at the cursor and automatically flipped when it would overflow the container's edges.

JSClickPosition

You'll get access to the interactive demo with a free account.

This effect is part of Effect.Labs — 811 vanilla effects, some free, some premium. Navigation has 36 effects, including 4 free. Explore the category →

3 usage examples

Content editor — Actions on selected element — Context Menu example 1

① Content editor — Actions on selected element

WhenThe user right-clicks a node, block, or object inside a design editor, rich-text editor, or wireframing tool.
WhyThe menu appears exactly at the cursor and never escapes the editing area — even in corners — thanks to automatic edge-flip. No navigation to a side panel is required.
SettingsItems: ✎ Edit / 📋 Copy / 📄 Duplicate / [separator] / 🗑 Delete. Background #1e1b4b, min-width: 180px, border-radius: 10px.
Dashboard — Quick actions on a table row — Context Menu example 2

② Dashboard — Quick actions on a table row

WhenRight-click on a data table row (CRM, analytics, order management) to access actions without leaving the current context.
WhyThe context menu avoids opening a detail page for common actions (export, archive, duplicate) — measurable time savings on high-volume repetitive workflows.
SettingsItems: 👁 View detail / ⬇ Export CSV / 📄 Duplicate / [separator] / 🗑 Archive. Background #111827, border rgba(75,85,99,.3), text #d1d5db.
Asset manager — Right-click on a file thumbnail — Context Menu example 3

③ Asset manager — Right-click on a file thumbnail

WhenIn a CMS, media manager, or embedded file explorer, the user right-clicks a thumbnail to access file operations.
WhyThe effect replicates familiar OS file-explorer behavior, reducing the learning curve — users find actions without hunting through nested menus.
SettingsItems: ✎ Rename / 📁 Move / ⬇ Download / [separator] / 🗑 Delete. min-width: 160px, separator before Delete to signal the destructive action.

How it works

The contextmenu event is captured with preventDefault() to suppress the browser's native menu. The opening position is computed in container-relative coordinates: clientX - rect.left / clientY - rect.top — the menu opens exactly where the cursor is, regardless of the page's scroll position.

The menu is first moved off-screen (left: -9999px) so the browser renders it as visible but imperceptible. A requestAnimationFrame then waits for the browser to compute its actual dimensions via getBoundingClientRect(). This one-frame delay is the prerequisite for knowing the menu's exact width and height before placing it definitively.

The edge-flip logic compares position + width (and height) of the menu against the container's dimensions. If the menu would overflow to the right, it is shifted to containerWidth - menuWidth - 8 px; same for the bottom. A floor at 4 px prevents any overflow to the left or top.

The .open class triggers the appearance via a CSS transition (opacity 0→1, scale 0.95→1, 150 ms). Closing fires on any outside click (document-level click listener) or the Escape key.

Accessibility

  • prefers-reduced-motion not handled: the appearance transition (opacity + scale, 150 ms) applies to all users, including those with "reduce motion" enabled in their OS. Add @media (prefers-reduced-motion: reduce) { .ctx-menu { transition: none } } if needed.
  • Menu items are native <button> elements — keyboard-focusable and correctly announced by screen readers.
  • No ARIA menu / menuitem roles are applied, and no aria-expanded is set on the trigger element — full ARIA context-menu semantics are not implemented in this code.
  • Closing via Escape is implemented through a keydown listener on document.
  • Keyboard shortcut hints (Ctrl+C, Del…) displayed in items are decorative text — they are not bound to actual handlers in this code.

Browser compatibility

Uses only the contextmenu event, getBoundingClientRect, and requestAnimationFrame — available in all modern browsers without polyfill.

Chrome 80+✓ Full
Firefox 75+✓ Full
Safari 13+✓ Full
Edge 80+✓ Full
iOS Safari✓ Long-press → contextmenu
Android Chrome✓ Long-press → contextmenu

If JS is disabled, no custom menu appears and the browser's native menu remains active — no errors, the page stays functional.

The code

HTML structure to paste into your page (CSS + JS available with a premium account):

index.html — structure
<div class="context-scene" id="contextScene">
  <!-- Clickable area content (text, image, objects…) -->

  <div class="ctx-menu" id="ctxMenu">
    <button class="ctx-item">
      <span class="ctx-icon">✎</span>
      Edit
      <span class="ctx-key">Ctrl+E</span>
    </button>
    <button class="ctx-item">
      <span class="ctx-icon">📋</span>
      Copy
      <span class="ctx-key">Ctrl+C</span>
    </button>
    <div class="ctx-sep"></div>
    <button class="ctx-item">
      <span class="ctx-icon">🗑</span>
      Delete
      <span class="ctx-key">Del</span>
    </button>
  </div>
</div>
🔒 Unlock the full code — from €2.99 the first month

Full HTML + CSS + JS, copy-paste ready — with hundreds of premium effects.

Customize

Options passed to the API or data-* attributes:

Option / propertyDefaultEffect
.ctx-menu { background } #1e1b4b Menu background color — replace with your brand surface color (e.g. #111827 for dark gray, #1c1c1e for a macOS-style look).
.ctx-menu { border-color } rgba(99,102,241,.2) Border and hover accent color — align with your primary color.
.ctx-item { color } #c7d2fe Item text color at rest. Increase contrast to #e2e8f0 for a better WCAG ratio.
.ctx-menu { min-width } 170px Minimum menu width — increase to 200–220 px if your labels are long or icons are wider.
.ctx-menu { border-radius } 12px Menu corner radius. Use 6px for a compact style or 16px for a macOS feel.
Edge margin (JS — value 8 in flip logic) 8 px Safety gap between the menu and the container edge during flip. Increase if your container has internal padding.

FAQ

e.preventDefault() in the contextmenu handler already suppresses the native menu on the targeted area (.context-scene). If you embed this inside an iframe, also add preventDefault() in the iframe's own listener — events do not automatically cross frame boundaries.
This component does not handle sub-menus natively. To add them, intercept mouseenter on the parent item and position a second .ctx-menu to the right (or left if the first is near the right edge), reusing the same getBoundingClientRect calculation logic. The existing click listener on document will naturally close all levels at once.
On iOS Safari and Android Chrome, a long-press triggers the contextmenu event — so the custom menu also appears on mobile. On some browsers the native menu may flash briefly before being suppressed; adding -webkit-touch-callout: none and user-select: none to the target area reduces this behavior.