Skip to content

Context menu

A context menu opens at the pointer on right-click — and from the keyboard via the ContextMenu key or Shift+F10 on the focused region. The panel reuses the Menu markup (.re-menu__panel / .re-menu__item); only the positioning differs (fixed, at the pointer, above modals). With no JS the native browser context menu is the fallback — enhanceContextMenu only preventDefaults the event once it’s wired.

import { enhanceContextMenu } from "@relements/core/behaviors/context-menu";
enhanceContextMenu(document);

Mark the trigger region with data-re-context-menu="<panel-id>" and make it focusable (tabindex="0") so keyboard invocation works.

Right-click (or focus + press the Menu key / Shift+F10) anywhere in this region.
Live example
<div class="re-context-region" data-re-context-menu="ctx-1" tabindex="0">
  Right-click (or focus + press the Menu key / Shift+F10) anywhere in this region.
</div>

<div
  id="ctx-1"
  class="re-menu__panel re-context-menu__panel"
  role="menu"
  aria-label="Row actions"
  hidden
>
  <button class="re-menu__item" role="menuitem" type="button" data-value="open">Open</button>
  <button class="re-menu__item" role="menuitem" type="button" data-value="rename">
    Rename
  </button>
  <button class="re-menu__item" role="menuitem" type="button" data-value="duplicate">
    Duplicate
  </button>
  <div class="re-menu__separator" role="separator"></div>
  <button class="re-menu__item" role="menuitem" type="button" data-value="delete">
    Delete
  </button>
</div>
  • Dispatches re-select (bubbles, cancelable; detail = { item, value, originalEvent }) on the region — preventDefault() keeps the menu open.
  • The panel is position: fixed at z-index: var(--re-z-dialog), so a context menu opened inside a modal sits above it.
  • The Popover API (top layer) is used as a bonus on browsers that support it; the fixed-position path is canonical (and what runs on the support floor).
  • Keyboard — open the menu with right-click, the ContextMenu key, or Shift+F10 while the region is focused; opening moves focus to the first item. Within the menu: Arrow Up / Arrow Down move between items and wrap, Home / End jump to first / last, and first-character typeahead focuses the next matching item. Escape and Tab both close the menu and return focus to the region; Enter / Space activate the focused <button>.
  • Focus — items are native buttons with a 2px inset :focus-visible ring (the panel is position: fixed, so an inset ring stays visible against the panel edge); the region itself shows a focus ring when tabbed to. Closing via Escape, Tab, or selection returns focus to the region so native Tab order resumes from a defined point.
  • Semantics — the panel is role="menu" with an aria-label; each item is role="menuitem", and the divider is role="separator". Once enhanceContextMenu wires a region it gains aria-haspopup="menu" and aria-controls pointing at the panel, so assistive tech announces that a menu is available.
  • Notes — with no JS the custom menu never opens and the native browser context menu is the fallback; the behavior only preventDefaults the contextmenu event once it’s wired. See the accessibility guide and the shared Menu markup it reuses.