---
## Menu
Action menu (`role="menu"`) triggered by a button, with nested
submenus, sections, icons, and separators. Items are verbs the user can
invoke — Edit, Delete, Share, Export — and dispatch an `onSelect` callback
when activated. An item given `checked` additionally *shows* a setting:
it renders as `role="menuitemradio"` with the supplied state announced and
marked. Menu still owns no selection state — the consumer computes `checked`
and updates it from `onSelect`. For committing a value to a form use
`Select` (or `Combobox` for searchable). Menu and Select stay deliberately
disjoint: Menu's `role="menu"`/`menuitem` semantics with arrow-key roving
and Action-family chrome versus Select's `role="listbox"` value commitment
with Form-family chrome.
**Import:** `import { Menu } from '@urbicon-ui/blocks';`
### Examples
```svelte
edit() },
{ label: 'Duplicate', onSelect: () => duplicate() },
{ type: 'section', label: 'Danger' },
{ label: 'Delete', onSelect: () => confirmDelete() }
]} />
```
```svelte
rename()}>Rename
duplicate()}>Duplicate
confirmDelete()}>Delete
```
```svelte
(sortBy = 'Name') },
{ label: 'Date', checked: sortBy === 'Date', onSelect: () => (sortBy = 'Date') }
]
},
{ label: 'Refresh', onSelect: () => refresh() }
]} />
```
```svelte
{#snippet customTrigger(toggle, open)}
{/snippet}
```
### Variants
- chevronAnimation: fade, none, rotate, translate (default: rotate)
- disabled: true
- itemSize: lg, md, sm (default: md)
- open: true (default: false)
- placement: bottom, bottom-end, bottom-start, top, top-end, top-start (default: bottom-start)
- syncWidth: false, true (default: true)
- false — The panel ignores the trigger and takes a 12rem minimum width instead — in a context menu as well — so it can end up narrower than a wide trigger.
- true — The panel is at least as wide as the trigger, and longer items widen it. A context menu has no trigger, so its panel is as wide as its items.
- tier: commit, modify (default: commit)
- usePortal: false, true (default: true)
### Api
| Prop | Type | Required | Default | Description |
| --- | --- | :---: | --- | --- |
| ...HTMLAttributes | `HTMLAttributes` | no | | HTML attributes (excluding: 'children' | 'class' | 'placeholder') |
| ...MenuVariants | `VariantProps` | no | | Styling variants from MenuVariants |
| chevronAnimation | `'rotate' | 'translate' | 'fade' | 'none'` | no | | chevronAnimation property |
| children | `Snippet` | no | | Declarative children mode — use `` / `` / ``. A `` wraps the items it names, the same way a `{ type: 'section' }` entry owns the items that follow it. |
| class | `string` | no | | class property. In the same Tailwind bucket `class` wins over the `slotClasses` entry for the element it lands on. Presets and provider defaults sit below both. |
| contextTrigger | `Snippet` | no | | Turn the menu into a **context menu**: instead of a trigger button, the snippet you pass becomes a right-click target. A `contextmenu` (right-click or long-press) on it opens the menu at the cursor position — the native browser context menu is suppressed. Keyboard navigation, dismissal and item selection behave exactly as in the dropdown menu; on dismiss, focus returns to wherever it was. Mutually exclusive with `customTrigger`/the default trigger button (when set, no trigger button renders). |
| customFooter | `Snippet` | no | | Optional custom footer rendered below the items list. |
| customHeader | `Snippet` | no | | Optional custom header rendered above the items list. |
| customItem | `Snippet<[TItem]>` | no | | Custom per-item content. **Render visible content only** — the outer `role="menuitem"` button is provided by Menu and handles the click / keyboard activation. Putting an interactive element (``, ``) inside the snippet creates nested-interactive HTML and triggers the item's action twice via event bubbling. Positional arg: `(item)`. To dispatch from outside the normal click (e.g. a "Recent" entry that needs to activate from a parent shortcut), call the item's own `onSelect` directly. |
| customTrigger | `Snippet<[() => void, boolean, () => void]>` | no | | Replace the default trigger button (chevron + label) with a custom element. Positional args: `(toggle, open, dismiss)`. - `toggle`: flips the open state — wire this to your custom trigger's click handler so the menu can be opened from the consumer's element. - `open`: current open state — useful for `aria-expanded`. - `dismiss`: closes the menu without changing toggle history; rarely needed but provided for symmetry. The consumer's element should bind its `onclick` to `toggle` and set `aria-expanded={open}` + `aria-haspopup="menu"` for ARIA correctness. |
| getItemChecked | `(item: TItem) => boolean | undefined` | no | | Optional checked-state resolver for items in array mode (`undefined` = plain action item). |
| getItemChildren | `(item: TItem) => TItem[] | undefined` | no | | getItemChildren property |
| getItemClass | `(item: TItem) => string | undefined` | no | | Optional per-item class resolver for items in array mode. |
| getItemDetail | `(item: TItem) => string | undefined` | no | | Optional right-aligned detail-text resolver for items in array mode. |
| getItemDisabled | `(item: TItem) => boolean` | no | | getItemDisabled property |
| getItemIcon | `(item: TItem) => unknown` | no | | Optional icon resolver for items in array mode. |
| getItemId | `(item: TItem) => string` | no | | getItemId property |
| getItemLabel | `(item: TItem) => string` | no | | Optional mapping functions for custom item shapes. |
| getSectionLabel | `(item: MenuSectionHeader) => string` | no | | Section label override. Accepts concrete section header type. |
| id | `string` | no | | id property |
| intent | `ButtonVariants['intent']` | no | 'neutral' | Button intent applied to the default trigger button. |
| isDivider | `(item: MenuItemType) => boolean` | no | | Divider detection override, symmetric to `isSection`. Supply it when your own item shape carries a `type: 'divider'` field of its own meaning — the built-in check is structural, and without this mapper such a row would render as a rule and lose its `onSelect`. This mapper and `isSection` *replace* the built-in check rather than narrowing it, so the escape is all-or-nothing: `isDivider={() => false}` also switches off genuine `{ type: 'divider' }` entries, which render as plain rows again — express those in your own shape instead. |
| isSection | `(item: MenuItemType) => boolean` | no | | Section detection override. Applies to full union, not just TItem. |
| items | `TItem[]` | no | | Array of menu items. Each item's `onSelect` runs when activated. |
| loading | `boolean` | no | false | Whether the default trigger button is in loading state. |
| mint | `MintProp` | no | 'none' | Micro-interaction preset forwarded to the inner default trigger Button and applied to each menu item row (per-item via context). Only applies while not disabled. |
| onOpenChange | `(open: boolean) => void` | no | | Fires when the menu opens or closes from user interaction (trigger click, item activation, Escape, Tab-out, outside click). Receives the new open state. Not called when the consumer writes `bind:open` directly. |
| placeholder | `string` | no | | Placeholder text shown on the default trigger. Acts as the trigger's accessible name unless `aria-label` is supplied. Typical values: "Actions", "More", "Options". Ignored when `customTrigger` is provided. |
| preset | `string` | no | | Apply a named preset registered via ``. Prefer this over `class` overrides when the requested look falls outside the semantic intent palette — presets keep hover/active/dark-mode logic coherent and make the custom look reusable across the project. |
| size | `ButtonVariants['size']` | no | 'md' | Button size applied to the default trigger button. |
| slotClasses | `Partial>` | no | | Per-slot class overrides merged with the variant styles. Slots: base | trigger | triggerText | chevron | content | header | section | group | divider | items | item | indicator | detail | submenu | footer. In the same Tailwind bucket `class` wins over the `slotClasses` entry for the element it lands on. Presets and provider defaults sit below both. |
| unstyled | `boolean` | no | false | Remove the default variant classes, the default trigger Button's included. Only user-supplied classes apply. |
| variant | `ButtonVariants['variant']` | no | 'outlined' | Button variant applied to the default trigger button. |
| disabled | `'true'` | no | | Controls the disabled behavior and appearance of the Menu component. Available options: true. |
| itemSize | `'lg' | 'md' | 'sm'` | no | md | Controls the itemSize behavior and appearance of the Menu component. Available options: lg, md, sm. |
| open | `'true'` | no | false | Controls the open behavior and appearance of the Menu component. Available options: true. |
| placement | `'bottom' | 'bottom-end' | 'bottom-start' | 'top' | 'top-end' | 'top-start'` | no | bottom-start | Controls the positioning and alignment of the Menu relative to its container or trigger element. Available options: bottom, bottom-end, bottom-start, and 3 more. |
| syncWidth | `'false' | 'true'` | no | true | Controls the syncWidth behavior and appearance of the Menu component. Available options: false, true. |
| tier | `'commit' | 'modify'` | no | commit | Selects the semantic radius tier of the Menu — the shape family it belongs to (`--radius-commit`/`-modify`/`-contain`/`-bridge`). Shape is retuned per family in your theme, so this picks the family rather than a pixel value. Available options: commit, modify. |
| usePortal | `'false' | 'true'` | no | true | Controls the usePortal behavior and appearance of the Menu component. Available options: false, true. |
Inherited from:
- MenuVariants (external)
- Omit, keyof MenuVariants> (omit-pattern)
- MenuCustomSlots (local-interface)
- AnimationProps (resolved-interface)
- Omit, 'children' | 'class' | 'placeholder'> (omit-pattern)
### Types
```ts
type MenuOption = string
```
```ts
interface MenuSectionHeader {
type: 'section';
label: string;
disabled?: true;
}
```
```ts
interface MenuDividerItem {
type: 'divider';
}
```
- `interface MenuObjectOption` — 10 members, definition omitted (65 lines; full definition: packages/blocks/src/lib/primitives/Menu/index.ts)
```ts
type MenuItemType = MenuOption | MenuObjectOption | MenuSectionHeader | MenuDividerItem
```
```ts
type MintProp = | MintName
| { name: MintName; config?: MintConfig & Record }
| Array
| Array }>
```
```ts
type InteractiveTier = 'commit' | 'modify'
```
```ts
type MintName = BuiltinMintName | 'none' | (string & {})
```
```ts
interface MintConfig {
/**
* `hover` and `focus` hold the effect while the pointer/visible focus stays
* on the element; `click` and `load` run it once.
*/
trigger?: 'hover' | 'click' | 'focus' | 'load';
/**
* Effect duration in ms. Written as an inline per-effect custom property
* (`--blocks-mint--duration`), so the CSS transition/animation
* actually runs at this speed; unset, the theme duration tokens apply.
*/
duration?: number;
/** Delay in ms before the effect applies. */
delay?: number;
/**
* CSS easing for the effect (`--blocks-mint--easing` inline);
* unset, the per-effect theme default applies.
*/
easing?: string;
disabled?: boolean;
}
```
```ts
type VariantProps = Omit<
Exclude[0], undefined>,
'class'
>
```
```ts
type BuiltinMintName = (typeof BUILTIN_MINT_NAMES)[number]
```
### Slots (slotClasses keys)
`base`, `trigger`, `triggerText`, `chevron`, `content`, `header`, `section`, `divider`, `items`, `item`, `indicator`, `detail`, `group`, `submenu`, `footer`