Menu
In reviewLive · web
Click a trigger to drop its menu, anchored and clamped to the stage
Open with a click or Enter / Space / ↓. In the menu: ↑ ↓ move, → opens a submenu, ← / Esc back, Enter runs.
Last action: none yet · wrap on · sort recent. Pointer + keyboard both work; the island is live.
// Web: React, consuming @dizyx/nockerl-tokens. (Web is the laggard platform. Spec + live demo,// not yet exported from @dizyx/nockerl-react; promotion is tracked.)import { NockerlMenu, MenuItem } from '@dizyx/nockerl-react';
export function SessionOverflow() { const [wrap, setWrap] = useState(true); return ( <NockerlMenu trigger={<NockerlIconButton icon="kebab" label="Session actions" />} align="end"> <MenuItem icon="rename" shortcut="⌘R" onSelect={rename}>Rename</MenuItem> <MenuItem icon="settings" onSelect={editModel}>Edit provider / model</MenuItem> <MenuItem icon="fork" onSelect={fork}>Fork</MenuItem>
<MenuSection label="View" /> <MenuItem icon="wrap" checked={wrap} onSelect={() => setWrap((v) => !v)}>Wrap lines</MenuItem>
<MenuSub icon="export" label="Export as"> <MenuItem onSelect={() => exportAs('md')}>Markdown (.md)</MenuItem> <MenuItem onSelect={() => exportAs('json')}>JSON (.json)</MenuItem> </MenuSub>
<MenuSeparator /> <MenuItem icon="trash" tone="danger" onSelect={confirmDelete}>Delete</MenuItem> </NockerlMenu> );}// Android: Jetpack Compose (canonical). The session-overflow menu from// chat/ui/SessionChipsBar.kt: a DropdownMenu of DropdownMenuItem rows, each with// a leadingIcon + label; Delete is the destructive tail. (Material 3 has no// checkbox/radio menu item, so a checked row shows a trailing Check icon.)var expanded by remember { mutableStateOf(false) }
Box { NockerlIconButton( icon = Icons.Filled.MoreVert, contentDescription = "Session actions", onClick = { expanded = true }, ) DropdownMenu(expanded = expanded, onDismissRequest = { expanded = false }) { DropdownMenuItem( text = { Text("Rename") }, leadingIcon = { Icon(Icons.Outlined.Edit, contentDescription = null) }, onClick = { expanded = false; onRename() }, ) DropdownMenuItem( text = { Text("Edit provider / model") }, leadingIcon = { Icon(Icons.Outlined.Settings, contentDescription = null) }, onClick = { expanded = false; onEdit() }, ) DropdownMenuItem( text = { Text("Fork") }, leadingIcon = { Icon(Icons.AutoMirrored.Filled.CallSplit, contentDescription = null) }, onClick = { expanded = false; onFork() }, ) HorizontalDivider() // Checked row → trailing Check; toggling keeps the menu open. DropdownMenuItem( text = { Text("Wrap lines") }, leadingIcon = { Icon(Icons.Outlined.WrapText, contentDescription = null) }, trailingIcon = { if (wrapLines) Icon(Icons.Outlined.Check, contentDescription = "On") }, onClick = { onToggleWrap() }, ) HorizontalDivider() DropdownMenuItem( text = { Text("Delete", color = MaterialTheme.colorScheme.error) }, leadingIcon = { Icon(Icons.Outlined.Delete, contentDescription = null, tint = MaterialTheme.colorScheme.error) }, onClick = { expanded = false; onDelete() }, ) }}// macOS: SwiftUI (canonical). UI/AppSettingsView.swift + MenuBarContent.swift:// a Menu whose label is the trigger; Section groups + Divider() separate; a// checked row shows a leading checkmark via Label(_, systemImage: "checkmark");// Button(role: .destructive) is the destructive item; a nested Menu is a submenu.Menu { Button { rename() } label: { Label("Rename", systemImage: "pencil") } .keyboardShortcut("r", modifiers: .command) Button { editModel() } label: { Label("Edit provider / model", systemImage: "gearshape") } Button { fork() } label: { Label("Fork", systemImage: "arrow.triangle.branch") }
Section("View") { // A checked row = a leading checkmark; unchecked = plain text. Button { wrapLines.toggle() } label: { if wrapLines { Label("Wrap lines", systemImage: "checkmark") } else { Text("Wrap lines") } } }
Menu("Export as") { // nested Menu = a submenu Button("Markdown (.md)") { exportAs(.md) } Button("JSON (.json)") { exportAs(.json) } }
Divider() Button(role: .destructive) { confirmDelete() } label: { Label("Delete", systemImage: "trash") }} label: { Image(systemName: "ellipsis")}.menuStyle(.borderlessButton).menuIndicator(.hidden)Parameters
Section titled “Parameters”| Prop | Type | Default | Description |
|---|---|---|---|
trigger * | ReactNode | 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 | The menu contents: item rows, full-width separators, section labels, and nested submenus. | |
MenuItem.icon | string | Optional leading glyph. The icon column is reserved menu-wide so every label aligns. | |
MenuItem.shortcut | string | Trailing keyboard-shortcut hint (e.g. ⌘R), rendered in the mono font, muted. | |
MenuItem.checked | boolean | 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 | Activation handler. Action items close the menu; checkable items toggle in place. |
| Parameter | Type | Default | Description |
|---|---|---|---|
expanded * | Boolean | Whether the menu is open. Set true from the trigger's onClick. | |
onDismissRequest * | () -> Unit | 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 | 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 | 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 | The dropdown. label is the trigger; content is the item list. Owns its own open/dismiss + keyboard. | |
Button(action:label:) | View | One item row. A leading Label(_, systemImage: "checkmark") marks a checked row; plain Text when unchecked. | |
Section(_:) | View | A titled group: the section label + implicit grouping (the wayfinding caption). | |
Divider() | View | A full-width separator between groups of items. | |
Menu(_:content:) | View | A nested menu = a submenu, opened to the side on hover / focus. | |
Button(role: .destructive) | View | The destructive item, system-tinted red (maps to the error token). | |
.keyboardShortcut(_:modifiers:) | modifier | Binds a key + modifier (e.g. "r", .command) and surfaces the ⌘-hint on the row. |