Menus
Floating list of choices anchored to a trigger — opens on demand, closes after a choice or dismiss. Material parity (`/components/menus`). Covers dropdown (anchor button), context (long-press / right-click), submenu (cascading), and exposed dropdown menu (text field with selection list).
Spec — Menus
Facet *isual*of Koder Design. Material parity: https://m3.material.io/components/menus.
3 variants
| Variant | Trigger | Use |
|---|---|---|
| *ropdown* | Tap / click a button (icon / overflow ⋮ / text) | List of actions or selection options |
| *ontext* | Long |
Operations on the current target |
| *xposed dropdown* | Tap a text field | Selection from a closed list (replaces native <select>) |
Pick by trigger type. Dropdown is the default.
Anatomy
┌──────────────────┐
│ ✂ Cut ⌘X │
Trigger ───→ │ ⏎ Copy ⌘C │
(button or field) │ 📋 Paste ⌘V │
│ ────────────── │
│ ⚙ Settings │
│ ❌ Delete › │
└──────────────────┘- *idth* min 112 dp; max 320 dp; auto-fit to longest item
- *tem height* 48 px (single-line label)
- *tem padding* 16 px horizontal, 12 px vertical
- *ontainer radius* 4 px (Material 3 default)
- *ontainer bg*
surface-container - *levation* 2 dp (shadow + tonal overlay)
- *tem label*
body-large - *eading icon* 24 px, optional
- *railing* keyboard shortcut OR submenu chevron (›)
- *ivider* inset divider between item groups (optional)
R1 — Anchor / positioning
Menu opens relative to trigger:
| Anchor | Position |
|---|---|
| Below trigger | Default for dropdowns |
| Above trigger | When below would overflow viewport |
| Right of trigger | Submenu (cascading) |
| Left of trigger | Submenu RTL |
| At cursor | Context menu (right-click) |
Position adjusts at runtime to stay inside viewport — flip / shift within 8 px of edge.
R2 — Items
| Item type | Use |
|---|---|
| *ction* | Label + optional icon + optional shortcut → performs action and closes menu |
| *oggle* | Label + check mark on selected → toggles state, may stay open or close (document) |
| *roup label* | Subheader text, non-interactive |
| *ivider* | Separates groups (full-width inside menu) |
| *ubmenu* | Label + trailing › → opens cascading submenu |
| *isabled* | 38% opacity, not interactive |
Max ~10 items recommended; > 10 → use exposed-dropdown style with search (combobox).
R3 — Keyboard shortcut display
Trailing shortcut is label-small (11/16), text-muted, right-aligned:
| OS | Format |
|---|---|
| macOS | ⌘C (symbol) |
| Windows / Linux | Ctrl+C (text) |
| Cross-platform binding | Show OS-specific format detected at runtime |
Don't show shortcut on touch-only surfaces — useless and clutters.
R4 — Submenu (cascading)
Submenu opens to the right (or left in RTL):
- Trigger item has trailing chevron (›)
- Hover / focus on trigger item: submenu opens after 200 ms delay
- Submenu items follow same anatomy as parent menu
- Esc closes only the deepest submenu; outside click closes everything
- Max depth: 2 levels (parent → submenu → no further nesting)
R5 — Context menu
Triggered by:
- Right-click (desktop)
- Long-press (touch, ~500 ms)
- Keyboard Menu key / Shift+F10
Opens at the cursor / touch position, NOT anchored to a trigger element.
Common content:
- Cut / Copy / Paste (text fields)
- Select all / Clear (lists)
- Open / Rename / Delete (file items)
- Inspect (web)
R6 — Exposed dropdown menu (combobox replacement)
Text field that opens a menu of options on focus / tap:
┌──────────────────────────┐
│ Country ⌄ │
└──────────────────────────┘
↓ (tap)
┌──────────────────────────┐
│ Country ⌃ │
│ ────────────────────── │
│ 🇧🇷 Brazil │
│ 🇺🇸 United States │
│ 🇨🇦 Canada │
│ ... │
└──────────────────────────┘- Trigger looks like an Outlined or Filled text field with trailing
chevron
- Menu opens below; max-height ~280 dp, scrolls
- Typing in field filters options (combobox mode); pure menu mode
doesn't filter
- Selecting an option fills the field text and closes the menu
Use this *nstead of*native <select> for branded Koder UIs.
R7 — States
| State | Visual |
|---|---|
| Item rest | Base |
| Item hover | State layer 8% on item |
| Item pressed | State layer 12% |
| Item focused (keyboard) | 2 px focus ring on item, OR row tint |
| Item selected (toggle) | Trailing check icon |
| Item disabled | 38% opacity |
Open / close animation:
- *pen* scale 0.95 → 1 from anchor edge + fade
in (motionfast) - *lose* scale 1 → 0.95 + fade
out (motionfast) - Reduced motion: instant in / out
R8 — Dismiss behaviors
| Trigger | Result |
|---|---|
| Tap an action item | Closes menu, runs action |
| Tap a toggle item | Toggles state, may stay open |
| Tap outside menu | Closes menu, no action |
| Esc key | Closes menu |
| Scroll page (some surfaces) | Closes menu |
| Resize window | Repositions or closes |
R9 — Accessibility
- Trigger:
aria-haspopup="menu"+aria-expanded="true"when open - Menu container:
role="menu" - Each item:
role="menuitem"(action),role="menuitemcheckbox"(toggle),
role="menuitemradio"(single-select group) - Submenu trigger:
aria-haspopup="menu"+aria-expanded - Group label:
role="none"on label +role="group"on items group - Divider:
role="separator" - Keyboard:
- Arrow Down: move to next item (wrap to first)
- Arrow Up: previous (wrap to last)
- Arrow Right: open submenu (if focused on submenu trigger)
- Arrow Left: close submenu
- Home / End: first / last
- Enter / Space: activate
- Esc: close
- Type-ahead: typing letter focuses next item starting with that
letter
R10 — Density
| Density | Item height | Padding |
|---|---|---|
| Compact | 36 px | 12 px / 8 px |
| Default | 48 px | 16 px / 12 px |
| Comfortable | 56 px | 20 px / 16 px |
R11 — Per-preset variation
| Preset | Container | Items |
|---|---|---|
material3 |
4 px radius, 2 dp elevation, surface-container | Tonal hover |
material2 |
4 px radius, 8 dp shadow, white bg | Filled hover |
ios_cupertino |
14 px radius, blur backdrop | Solid pressed bg |
gnome |
8 px radius, surface tonal | Solid blue hover |
windows_11 |
Mica backdrop, 8 px radius | Accent bar on focus |
brutalist |
Sharp, thick black border, no shadow | Inverted hover |
terminal_classic |
Bordered box, single line per item | > prefix on focus |
R12 — Forbidden patterns
- ❌ Menus that scroll horizontally
- ❌ Menus wider than 320 dp (too wide; use dialog or card)
- ❌ Items wrapping to multiple lines (truncate or shorten label)
- ❌ Submenu depth > 2 levels
- ❌ Menu that re-opens automatically after closing
- ❌ Menu without keyboard support
- ❌ Context menu with > 12 items at top level (overwhelming)
- ❌ Menu items as form controls (text fields, sliders) — use dialog
or popover instead
- ❌ Confirmation dialogs from menu items without warning (destructive
actions should be visually marked as destructive —
errorcolor)
Cross-link
themes/elevation.kmd— 2 dp tonal + shadow recipethemes/color-roles.kmd—surface-containerbackgroundthemes/typography.kmd—body-largefor items,label-smallfor shortcutsinteraction/states.kmd— hover / focused / pressed layerscomponents/lists.kmd— sibling pattern (lists are persistent menus)components/text-fields.kmd— exposed dropdown anchorfoundations/elements.kmd— Container + Control families