Overlay

ContextMenu

Right-click context menu with icons, keyboard shortcuts, checkbox/radio items, submenus, and custom rendering.

Basic Usage

Right-click on the trigger area to open the menu.

vacation-photo.jpg

2.4 MB · Right-click me

<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>

Keyboard Shortcuts

Show keyboard shortcuts with the kbds prop.

Text Editor

Right-click for 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

Toggle options with type: 'checkbox'.

Canvas Workspace

Grid: On · Rulers: Off

<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

Single-select with type: 'radio' and radioGroups.

File List

Sorted by: name

<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

Nested menus with type: 'sub'.

Project Files

Right-click for actions

<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 menu sizes.

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

Read-only Document

Some actions disabled

<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 internal elements.

SlotDescription
contentMain context menu content area
groupItem group container
separatorSeparator element
labelLabel/heading for groups
itemIndividual menu item
itemLeadingIconIcon area before label
itemLabelLabel text area
itemTrailingKbdsKeyboard shortcut area
itemIndicatorCheckbox/radio indicator
subTriggerSubmenu trigger item
subTriggerIconSubmenu indicator icon
subContentNested submenu content

Snippets

SnippetDescription
childrenRight-click trigger area (receives open state)
headerCustom header content (receives close function)
footerCustom footer content (receives close function)
itemCustom item rendering
itemLeadingCustom leading section (icon area)
itemLabelCustom label section
itemTrailingCustom trailing section (kbd shortcuts)
contentReplace entire items rendering

Props

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