The control that opens the menu on click / Enter / Space / ↓. Gets aria-haspopup + aria-expanded.
align
'start' | 'end'
'start'
Which trigger edge the menu aligns to. The menu flips up + clamps horizontally to stay on-screen.
children *
MenuItem | MenuSeparator | MenuSection | MenuSub
n/a
The menu contents: item rows, full-width separators, section labels, and nested submenus.
MenuItem.icon
string
n/a
Optional leading glyph. The icon column is reserved menu-wide so every label aligns.
MenuItem.shortcut
string
n/a
Trailing keyboard-shortcut hint (e.g. ⌘R), rendered in the mono font, muted.
MenuItem.checked
boolean
n/a
Renders the row as menuitemcheckbox (a leading cyan check). Toggling keeps the menu open.
MenuItem.tone
'default' | 'danger'
'default'
A danger item uses the error token and is divider-separated as the tail.
MenuItem.onSelect
() => void
n/a
Activation handler. Action items close the menu; checkable items toggle in place.
Parameter
Type
Default
Description
expanded *
Boolean
n/a
Whether the menu is open. Set true from the trigger's onClick.
onDismissRequest *
() -> Unit
n/a
Called when the menu should close (outside tap, back, Esc).
modifier
Modifier
Modifier
External modifier on the menu surface (e.g. fillMaxWidth(0.85f) for an anchored exposed dropdown).
item.text *
@Composable -> Unit
n/a
The row label, usually Text("…"). Use color = colorScheme.error for the destructive item.
item.leadingIcon
@Composable (() -> Unit)?
null
Optional icon before the label.
item.trailingIcon
@Composable (() -> Unit)?
null
Trailing slot. Render a Check for a selected row (M3 has no native checkable item).
item.enabled
Boolean
true
When false, the row is dimmed and non-interactive.
item.onClick *
() -> Unit
n/a
Row activation. Typically sets expanded = false then runs the action.
SwiftUI exposes the menu as Menu { content } label: { trigger }. There are no
explicit state parameters. Menu owns open/close. Rows are standard Buttons;
structure comes from Section, Divider, nested Menu, and Button(role:).
Style
Type
Default
Description
Menu(content:label:)
View
n/a
The dropdown. label is the trigger; content is the item list. Owns its own open/dismiss + keyboard.
Button(action:label:)
View
n/a
One item row. A leading Label(_, systemImage: "checkmark") marks a checked row; plain Text when unchecked.
Section(_:)
View
n/a
A titled group: the section label + implicit grouping (the wayfinding caption).
Divider()
View
n/a
A full-width separator between groups of items.
Menu(_:content:)
View
n/a
A nested menu = a submenu, opened to the side on hover / focus.
Button(role: .destructive)
View
n/a
The destructive item, system-tinted red (maps to the error token).
.keyboardShortcut(_:modifiers:)
modifier
n/a
Binds a key + modifier (e.g. "r", .command) and surfaces the ⌘-hint on the row.
Menu vs. long-press-pop vs. command-palette
Three sibling surfaces, three triggers. Don’t mix them:
Menu (this page) is click-triggered and anchored to a control (a kebab ⋯ or actions button). Non-modal, no dim, positioned under/beside its trigger.
Long-press pop is press-and-hold ON content (a bubble, a row): it dims the ground, lifts the target, and pops the same item vocabulary beside it.
Command palette is the modal ⌘K centered overlay with type-to-search, a global launcher, not a per-control action list. (The combobox is the type-to-filter select, not a menu.)
Cross-platform drift (tracked for reconciliation)
The item grammar is shared; the real divergences between the shipped platforms:
Surface ownership: web draws the menu surface itself (the canon overlay card: surface +
hairline + top catch-light + a neutral shadow). Android’s DropdownMenu and Voice’s SwiftUI
Menu (NSMenu-backed) use system-owned surfaces. Per Law 9 (honor the platform) they keep
native elevation, motion, and dismissal, so the web card recipe deliberately does not port 1:1.
Item vocabulary: web ships menuitemcheckbox / menuitemradio rows, section labels,
nested submenus, and mono shortcut hints. Android’s shipped usage is plain action rows
(DropdownMenuItem); Voice gets submenus + destructive roles from SwiftUI but has no
checkbox/radio item variant in use. Adopting the richer row kinds natively is the open item.
Point anchoring: web’s openAt(x, y) (context-menu positioning) has no shipped native
counterpart yet; Android/Voice menus anchor to their trigger control only.