Skip to content
Navigation

Menu

MD3 menu — a list of actions hanging off a trigger.

Walkthrough

1. Basic

The trigger snippet gets the props that wire it to the menu. The overflow on a row or a card, where three buttons would crowd it.

<script lang="ts">	import { Button, Menu, MenuItem } from 'omaris';</script>​<Menu label="Project actions">	{#snippet trigger(props)}		<Button variant="outlined" {...props}>Actions</Button>	{/snippet}​	<MenuItem>Rename</MenuItem>	<MenuItem>Duplicate</MenuItem>	<MenuItem tone="destructive">Delete</MenuItem></Menu>

2. Icons and shortcuts

Icons and shortcuts earn their place on a menu people open often; inset lines up a row without an icon beside the ones that have one.

<script lang="ts">	import { IconButton, Menu, MenuItem, MenuLabel } from 'omaris';</script>​<Menu label="Edit">	{#snippet trigger(props)}		<IconButton aria-label="Edit menu" {...props}>			<svg viewBox="0 0 24 24" fill="currentColor">				<circle cx="12" cy="5" r="1.6" /><circle cx="12" cy="12" r="1.6" /><circle					cx="12"					cy="19"					r="1.6"				/>			</svg>		</IconButton>	{/snippet}​	<MenuLabel>Edit</MenuLabel>	<MenuItem shortcut="⌘C">		{#snippet icon()}			...		{/snippet}		Copy	</MenuItem>	<MenuItem shortcut="⌘V">		{#snippet icon()}			...		{/snippet}		Paste	</MenuItem>	<MenuItem inset shortcut="⌘⌫" tone="destructive">Delete</MenuItem></Menu>

3. Selection

selected draws the tick. selected={false} is meaningfully different from leaving it off: it reserves the space, so a list of choices does not shuffle sideways when one is picked. radio makes each row one of a set, which is what a screen reader announces.

<script lang="ts">	import { Button, Menu, MenuItem } from 'omaris';​	let sort = $state('Newest first');</script>​<Menu label="Sort by" closeOnSelect>	{#snippet trigger(props)}		<Button variant="tonal" {...props}>Sort: {sort}</Button>	{/snippet}​	<MenuItem radio selected={sort === 'Newest first'} onclick={() => (sort = 'Newest first')}>		Newest first	</MenuItem>	<MenuItem radio selected={sort === 'Oldest first'} onclick={() => (sort = 'Oldest first')}>		Oldest first	</MenuItem>	<MenuItem radio selected={sort === 'Largest total'} onclick={() => (sort = 'Largest total')}>		Largest total	</MenuItem></Menu>

4. Submenus

MenuSub is a row that opens a menu of its own — on hover, a press or →. On a phone the menu is a bottom sheet and the submenu expands in place.

<script lang="ts">	import { Button, Divider, Menu, MenuItem, MenuSub } from 'omaris';​	let label = $state('None');</script>​<Menu label="Conversation">	{#snippet trigger(props)}		<Button variant="tonal" {...props}>Conversation</Button>	{/snippet}​	<MenuItem shortcut="E">Archive</MenuItem>	<MenuItem shortcut="M">Mute</MenuItem>	<MenuSub label="Label: {label}">		{#each ['None', 'Work', 'Family', 'Receipts'] as option (option)}			<MenuItem radio selected={label === option} onclick={() => (label = option)}>				{option}			</MenuItem>		{/each}	</MenuSub>	<Divider class="my-1" />	<MenuItem tone="destructive">Delete</MenuItem></Menu>

5. Placement

side and align place it, and it flips itself when there is no room. align="end" for a menu hanging off the far end of a toolbar.

<script lang="ts">	import { Button, Menu, MenuItem } from 'omaris';</script>​<Menu side="top" align="start" label="Upwards">	{#snippet trigger(props)}		<Button variant="outlined" size="xs" {...props}>Top</Button>	{/snippet}	<MenuItem>Opens above</MenuItem></Menu>​<Menu side="right" align="start" label="Sideways">	{#snippet trigger(props)}		<Button variant="outlined" size="xs" {...props}>Right</Button>	{/snippet}	<MenuItem>Opens beside</MenuItem></Menu>​<Menu matchWidth label="Matched">	{#snippet trigger(props)}		<Button variant="outlined" size="xs" class="w-48 max-w-full" {...props}>matchWidth</Button>	{/snippet}	<MenuItem>As wide as the trigger</MenuItem></Menu>