Containment
Context Menu
The right-click menu.
import { ContextMenu } from 'omaris' Examples
Basic
Right-click the card — or press and hold it on a touch screen.
Last action: nothing yet
<script lang="ts"> import { ContextMenu, MenuItem, Divider } from 'omaris'; let last = $state('nothing yet');</script><div class="flex flex-col items-center gap-3"> <ContextMenu label="File actions"> {#snippet trigger()} <div class="grid h-32 w-72 max-w-full place-items-center rounded-shape-md border border-dashed border-border bg-surface-container-low text-sm text-muted-foreground" > Right-click, or press and hold </div> {/snippet} <MenuItem onclick={() => (last = 'Open')} shortcut="⏎">Open</MenuItem> <MenuItem onclick={() => (last = 'Rename')} shortcut="F2">Rename</MenuItem> <Divider /> <MenuItem tone="destructive" onclick={() => (last = 'Delete')}>Delete</MenuItem> </ContextMenu> <p class="text-sm text-muted-foreground">Last action: {last}</p></div> Per row
One menu per row: each list item wraps its own, with its own actions.
Last action: nothing yet
<script lang="ts"> import { ContextMenu, MenuItem, MenuLabel, Divider } from 'omaris'; const files = ['report.pdf', 'notes.md', 'budget.xlsx']; let last = $state('nothing yet');</script><div class="flex flex-col items-center gap-3"> <div class="w-72 max-w-full overflow-hidden rounded-shape-md border border-border"> {#each files as file (file)} <ContextMenu label="{file} actions"> {#snippet trigger()} <div class="flex items-center justify-between border-b border-border px-3 py-2.5 text-sm last:border-b-0 hover:bg-surface-container" > {file} <span class="text-xs text-muted-foreground">right-click</span> </div> {/snippet} <MenuLabel>{file}</MenuLabel> <MenuItem onclick={() => (last = `Open ${file}`)}>Open</MenuItem> <MenuItem onclick={() => (last = `Share ${file}`)}>Share</MenuItem> <Divider /> <MenuItem tone="destructive" onclick={() => (last = `Delete ${file}`)}>Delete</MenuItem> </ContextMenu> {/each} </div> <p class="text-sm text-muted-foreground">Last action: {last}</p></div> Overridden
closeOnSelect={false} keeps the menu open while a set of toggles is worked through, and the surface takes its own width.
<script lang="ts"> import { ContextMenu, MenuItem, MenuLabel } from 'omaris'; let pinned = $state(true); let dense = $state(false);</script><ContextMenu label="View" closeOnSelect={false} classes={{ surface: 'min-w-64 rounded-shape-lg' }}> {#snippet trigger()} <div class="grid h-32 w-72 max-w-full place-items-center rounded-shape-md border border-dashed border-border bg-surface-container-low text-sm text-muted-foreground" > Toggles that stay open </div> {/snippet} <MenuLabel>View</MenuLabel> <MenuItem selected={pinned} onclick={() => (pinned = !pinned)}>Pinned first</MenuItem> <MenuItem selected={dense} onclick={() => (dense = !dense)}>Dense rows</MenuItem></ContextMenu> When to use it
Use it for
- The actions that belong to one thing: a file, a row, a card, a node in a Tree View. A visible button per row would clutter the list.
- A long list of actions people run quickly: open, rename, duplicate, share, delete.
- A power-user path in a dense surface like a board, a canvas or a table, next to the ordinary buttons, not instead of them.
Not for
- The only way to reach an action. A context menu is invisible until guessed at, so every row must also be reachable somewhere obvious: a Menu off a ⋯ button, a toolbar, a shortcut.
- A menu opened by a button → Menu.
- Choosing a value → Select or Combobox. This menu is for actions.
- The whole page. A menu on
<body>replaces the browser's own, which people use to open a link in a new tab.
Do
- Repeat the important rows somewhere visible. If the only way to delete is a right-click, the feature does not exist on a phone.
- Keep it to about seven rows, grouped with
Dividerand named withMenuLabelwhen the target is not obvious. Put the destructive row last, withtone="destructive", behind a divider. - Wrap the smallest region that owns the actions: one row, not the list.
- Leave
longPresson. It is the same gesture on a touch screen; off makes the menu desktop-only. - Set
closeOnSelect={false}when the rows are toggles.
Don't
- Nest submenus. A shortcut with a second level is not a shortcut.
- Use it on a link or a text selection. People want the browser's own menu there.
- Open it on left click. That is a Menu.
- Add a row that needs a second click somewhere else to make sense.
API
ContextMenu
The right-click menu.
Wraps a region and opens a Menu at the pointer when it is right-clicked — or long-pressed, which is the same gesture on a touch screen and the reason a context menu is not a desktop-only idea. The rows are the same MenuItem, MenuLabel and Divider a Menu takes, and choosing one closes the menu.
It is always a shortcut, never the only way to do something: every action in here must also be reachable from a button, a menu or the keyboard.
import { ContextMenu } from 'omaris' <ContextMenu> {#snippet trigger()} <Card>Right-click me</Card> {/snippet} <MenuItem onclick={rename}>Rename</MenuItem> <MenuItem tone="destructive" onclick={remove}>Delete</MenuItem></ContextMenu> Parts
Every element this renders is reachable from outside. Pass Tailwind for one part as classes={{ part: '…' }}; class covers the root and is merged last, so it always wins.
surface- No description in the source yet.
trigger- The wrapper around whatever opens the menu —
w-full, usually.
Props
open bindableDefaults to false
boolean Bindable.
size Defaults to 'md'
MenuSize disabled Defaults to false
boolean Never opens, and the browser's own menu comes back.
longPress Defaults to 500
number How long a touch has to be held before the menu opens, in ms. 0 turns the gesture off and leaves right-click as the only way in.
closeOnSelect Defaults to true
boolean Leave the menu open after a row is chosen — for a set of toggles.
label string Accessible name for the menu.
mobileSheet Defaults to true
boolean On a phone, rise from the bottom edge as a sheet — dragged down to dismiss, rows a thumb can hit — rather than opening under a finger that is covering it. false keeps it at the press point.
trigger Snippet The region that answers the right-click.
children Snippet The rows — MenuItem, MenuLabel, Divider.
onopen (position: { x: number; y: number }) => void Fires with the coordinates the menu opened at.
class string classes MenuClasses Per-part Tailwind overrides. class covers the wrapper.