SvContextMenu

Wraps a region and opens a menu at the pointer on right-click or long-press.

SvContextMenu turns any content into a right-clickable zone. It reuses SvMenuList for the surface itself - submenus, keyboard, icons, and shortcuts - clamps the menu so it stays on-screen, portals it to <body>, and closes through the shared dismissable layer stack. You describe the menu with a plain MenuItem[] array and handle picks in onSelect.

Basic usage

<script lang="ts">
  import { SvContextMenu, type MenuItem } from '@svgrid/grid'
  const items: MenuItem[] = [
    { label: 'Copy', shortcut: 'Ctrl+C', onSelect: () => copy() },
    { label: 'Duplicate', onSelect: () => duplicate() },
    { separator: true },
    { label: 'Delete', onSelect: () => remove() },
  ]
</script>

<SvContextMenu {items} onSelect={(i) => console.log(i.label)}>
  <div class="drop-zone">Right-click me</div>
</SvContextMenu>

Props

Prop Type Default Description
items ReadonlyArray<MenuItem> - The menu contents (see MenuItem below).
onSelect (item: MenuItem) => void - Fires when a leaf item is chosen.
disabled boolean false Suppress the context menu entirely.
ariaLabel string - Accessible name for the menu surface.
children Snippet - The region that responds to right-click.
Field Type Description
label string Item text. Omit and set separator for a divider.
separator boolean Render a divider instead of an item.
icon Snippet Leading icon.
shortcut string Right-aligned hint, e.g. a keyboard shortcut.
disabled boolean Dim and skip the item.
children MenuItem[] Nested submenu items (renders a flyout).
onSelect () => void Per-item callback fired when the item is chosen.

Patterns

Per-item handlers

Give each item its own onSelect so the array reads like a command list, and use the top-level onSelect only for cross-cutting logging:

const items: MenuItem[] = [
  { label: 'Edit', icon: pencil, onSelect: () => edit(row) },
  { label: 'Archive', onSelect: () => archive(row) },
]

Group related actions under a children array for a flyout, and break sections with { separator: true }:

const items: MenuItem[] = [
  { label: 'Move to', children: [
    { label: 'Inbox', onSelect: () => move('inbox') },
    { label: 'Archive', onSelect: () => move('archive') },
  ]},
  { separator: true },
  { label: 'Delete', disabled: locked, onSelect: () => remove() },
]

Row context menu on a grid

Build the item array from the row under the pointer so each action closes over the right record, and rebuild it when the selection changes. Disable actions that do not apply rather than hiding them, so the menu keeps a stable shape:

<script lang="ts">
  import { SvContextMenu, type MenuItem } from '@svgrid/grid'
  let row = $state({ id: 1, name: 'Ada', archived: false })

  const items: MenuItem[] = $derived([
    { label: 'Open', shortcut: 'Enter', onSelect: () => open(row) },
    { label: 'Duplicate', onSelect: () => duplicate(row) },
    { separator: true },
    { label: 'Archive', disabled: row.archived, onSelect: () => archive(row) },
    { label: 'Delete', onSelect: () => remove(row) },
  ])
</script>

<SvContextMenu {items} ariaLabel="Row actions" onSelect={(i) => log(i.label)}>
  <div class="row" onpointerdown={() => (row = pickRowUnderPointer())}>
    {row.name}
  </div>
</SvContextMenu>

Tip: the menu closes on page scroll, since a scrolled-away anchor would strand it. That means it is meant to be picked from promptly - do not rely on it staying open across a scroll.

Conditional menus

Bind disabled to context - for example, no menu on read-only rows - to skip the handler without unwrapping the region.

Accessibility

See also