Overlay

ContextMenu

Display a floating menu of actions triggered by right-click. Supports action items, checkbox items, radio groups, submenus, keyboard shortcuts, separators, and labels.

Basic Usage

Wrap any element with ContextMenu and pass an array of items. The menu appears on right-click.

vacation-photo.jpg

2.4 MB · Modified 2 days ago

<script lang="ts">
  import { ContextMenu, Modal, Button } from 'sv5ui';

  let showDeleteModal = $state(false);

  const items = [
    { label: 'Edit', icon: 'lucide:pencil' },
    { label: 'Duplicate', icon: 'lucide:copy' },
    { label: 'Archive', icon: 'lucide:archive' },
    { label: 'Delete', icon: 'lucide:trash-2', color: 'error', onSelect: () => showDeleteModal = true }
  ];
</script>

<ContextMenu {items}>
  <div class="rounded-xl border p-5">
    <p class="font-medium">vacation-photo.jpg</p>
    <p class="text-sm opacity-50">2.4 MB · Modified 2 days ago</p>
  </div>
</ContextMenu>

<Modal
  bind:open={showDeleteModal}
  title="Delete file?"
  description="This will permanently delete vacation-photo.jpg."
>
  {#snippet footer()}
    <div class="flex justify-end gap-2">
      <Button variant="outline" color="surface" label="Cancel" onclick={() => showDeleteModal = false} />
      <Button variant="solid" color="error" label="Delete" onclick={() => showDeleteModal = false} />
    </div>
  {/snippet}
</Modal>

With Keyboard Shortcuts

Use the kbds prop on items to display keyboard shortcut hints.

Right-click to see editing shortcuts

<script lang="ts">
  import { ContextMenu } from 'sv5ui';

  const items = [
    { label: 'Undo', icon: 'lucide:undo-2', kbds: ['meta', 'z'] },
    { label: 'Redo', icon: 'lucide:redo-2', kbds: ['meta', 'shift', 'z'] },
    { type: 'separator' },
    { label: 'Cut', icon: 'lucide:scissors', kbds: ['meta', 'x'] },
    { label: 'Copy', icon: 'lucide:copy', kbds: ['meta', 'c'] },
    { label: 'Paste', icon: 'lucide:clipboard', kbds: ['meta', 'v'] }
  ];
</script>

<ContextMenu {items}>
  <div class="rounded-xl border p-5">
    <!-- Text editor area -->
    Right-click to see editing shortcuts
  </div>
</ContextMenu>

Checkbox Items

Set type: 'checkbox' on items to render toggleable checkboxes. Use closeOnSelect: false to keep the menu open while toggling.

Canvas element

<script lang="ts">
  import { ContextMenu } from 'sv5ui';

  let showGrid = $state(true);
  let showRulers = $state(false);
  let snapToGrid = $state(true);

  let items = $derived([
    { type: 'label', label: 'View Options' },
    { type: 'separator' },
    { type: 'checkbox', label: 'Show Grid', checked: showGrid, closeOnSelect: false, onCheckedChange: (v) => showGrid = v },
    { type: 'checkbox', label: 'Show Rulers', checked: showRulers, closeOnSelect: false, onCheckedChange: (v) => showRulers = v },
    { type: 'checkbox', label: 'Snap to Grid', checked: snapToGrid, closeOnSelect: false, onCheckedChange: (v) => snapToGrid = v }
  ]);
</script>

<ContextMenu {items}>
  <div class="rounded-xl border p-5">
    <!-- Canvas workspace -->
  </div>
</ContextMenu>

Radio Items

Use type: 'radio' items with the radioGroups prop for single-select groups.

Documents Sorted by: name
report.pdf 1.2 MB
notes.md 24 KB
data.csv 856 KB
<script lang="ts">
  import { ContextMenu } from 'sv5ui';

  let sortBy = $state('name');

  const items = [
    { type: 'label', label: 'Sort By' },
    { type: 'separator' },
    { type: 'radio', label: 'Name', value: 'name' },
    { type: 'radio', label: 'Date Modified', value: 'date' },
    { type: 'radio', label: 'Size', value: 'size' }
  ];

  const radioGroups = [
    {
      name: 'sortBy',
      value: sortBy,
      onValueChange: (v) => sortBy = v
    }
  ];
</script>

<ContextMenu {items} {radioGroups}>
  <div class="rounded-xl border">
    <!-- File list -->
  </div>
</ContextMenu>

Submenu

Use type: 'sub' with a nested items array to create flyout submenus.

Project Files

3 items · 4.8 MB

<script lang="ts">
  import { ContextMenu } from 'sv5ui';

  const items = [
    { label: 'New File', icon: 'lucide:file-plus' },
    { label: 'New Folder', icon: 'lucide:folder-plus' },
    { type: 'separator' },
    {
      type: 'sub',
      label: 'Share',
      icon: 'lucide:share-2',
      items: [
        { label: 'Email', icon: 'lucide:mail' },
        { label: 'Message', icon: 'lucide:message-square' },
        { type: 'separator' },
        { label: 'More...', icon: 'lucide:more-horizontal' }
      ]
    },
    { type: 'separator' },
    { label: 'Delete', icon: 'lucide:trash-2', color: 'error' }
  ];
</script>

<ContextMenu {items}>
  <div class="rounded-xl border p-5">
    <!-- Folder card -->
  </div>
</ContextMenu>

Sizes

5 sizes from extra small to extra large.

XS
SM
MD
LG
XL
<ContextMenu {items} size="xs">...</ContextMenu>
<ContextMenu {items} size="sm">...</ContextMenu>
<ContextMenu {items} size="md">...</ContextMenu>
<ContextMenu {items} size="lg">...</ContextMenu>
<ContextMenu {items} size="xl">...</ContextMenu>

Disabled Items

Set disabled: true on individual items to prevent interaction.

Read-only Document

Some actions are unavailable for this item.

<script lang="ts">
  import { ContextMenu } from 'sv5ui';

  const items = [
    { label: 'Edit', icon: 'lucide:pencil' },
    { label: 'Duplicate', icon: 'lucide:copy', disabled: true },
    { label: 'Archive', icon: 'lucide:archive', disabled: true },
    { label: 'Delete', icon: 'lucide:trash-2', color: 'error' }
  ];
</script>

<ContextMenu {items}>
  <div class="rounded-xl border p-5">
    <!-- Read-only document -->
  </div>
</ContextMenu>

UI Slots

Use the ui prop to override classes on specific internal elements of the ContextMenu.

SlotDescription
contentThe floating menu panel — controls background, border, shadow, and rounding
groupGroups related menu items together — controls spacing between groups
separatorVisual divider between groups — controls color and spacing
labelNon-interactive heading within a group — controls font size, weight, color
itemIndividual menu item row — controls padding, hover state, cursor
itemIconIcon element within a menu item — controls icon size and color
itemLabelText label within a menu item — controls font size and color
itemKbdKeyboard shortcut badge — controls font, background, rounding
itemIndicatorIndicator for checkbox/radio items — controls size and color
subTriggerSubmenu trigger item — controls hover state and arrow indicator
subTriggerIconArrow icon on the submenu trigger — controls icon size and color
subContentSubmenu floating panel — controls background, border, shadow
checkboxIndicatorCheckbox check mark indicator — controls icon and color
radioIndicatorRadio dot indicator — controls icon and color

Snippets

Use Svelte 5 snippets to customize specific parts of the ContextMenu.

SnippetTypeDescription
childrenSnippet<[{ open: boolean }]>Right-click target area
contentSnippet<[{ open: boolean, close: () => void }]>Custom content replacing default item rendering
headerSnippet<[{ close: () => void }]>Custom header above the menu items
footerSnippet<[{ close: () => void }]>Custom footer below the menu items
itemSnippet<[ContextMenuItemSlotProps]>Custom renderer for individual menu items
itemLeadingSnippet<[ContextMenuItemSlotProps]>Custom leading section of menu items
itemLabelSnippet<[ContextMenuItemSlotProps]>Custom label section of menu items
itemTrailingSnippet<[ContextMenuItemSlotProps]>Custom trailing section of menu items

Props

PropTypeDefault
openbooleanfalse
itemsContextMenuItem[][]
size'xs' | 'sm' | 'md' | 'lg' | 'xl''md'
loopbooleantrue
radioGroupsRadioGroup[][]
transitionbooleantrue
portalbooleantrue
checkedIconstring'lucide:check'
submenuIconstring'lucide:chevron-right'
classstring-
uiRecord<Slot, Class>-