Skip to content

Menus

components specs/components/menus.kmd

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).

When this spec applies

Primary triggers

All triggers

Specification body

Spec — Menus

Facet Visual of Koder Design. Material parity: https://m3.material.io/components/menus.

3 variants

VariantTriggerUse
DropdownTap / click a button (icon / overflow ⋮ / text)List of actions or selection options
ContextLong-press (touch) or right-click (desktop)Operations on the current target
Exposed dropdownTap a text fieldSelection 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   ›  │
                     └──────────────────┘
  • Width: min 112 dp; max 320 dp; auto-fit to longest item
  • Item height: 48 px (single-line label)
  • Item padding: 16 px horizontal, 12 px vertical
  • Container radius: 4 px (Material 3 default)
  • Container bg: surface-container
  • Elevation: 2 dp (shadow + tonal overlay)
  • Item label: body-large
  • Leading icon: 24 px, optional
  • Trailing: keyboard shortcut OR submenu chevron (›)
  • Divider: inset divider between item groups (optional)

R1 — Anchor / positioning

Menu opens relative to trigger:

AnchorPosition
Below triggerDefault for dropdowns
Above triggerWhen below would overflow viewport
Right of triggerSubmenu (cascading)
Left of triggerSubmenu RTL
At cursorContext menu (right-click)

Position adjusts at runtime to stay inside viewport — flip / shift within 8 px of edge.

R2 — Items

Item typeUse
ActionLabel + optional icon + optional shortcut → performs action and closes menu
ToggleLabel + check mark on selected → toggles state, may stay open or close (document)
Group labelSubheader text, non-interactive
DividerSeparates groups (full-width inside menu)
SubmenuLabel + trailing › → opens cascading submenu
Disabled38% 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:

OSFormat
macOS⌘C (symbol)
Windows / LinuxCtrl+C (text)
Cross-platform bindingShow 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 instead of native <select> for branded Koder UIs.

R7 — States

StateVisual
Item restBase
Item hoverState layer 8% on item
Item pressedState layer 12%
Item focused (keyboard)2 px focus ring on item, OR row tint
Item selected (toggle)Trailing check icon
Item disabled38% opacity

Open / close animation:

  • Open: scale 0.95 → 1 from anchor edge + fade-in (motion-fast)
  • Close: scale 1 → 0.95 + fade-out (motion-fast)
  • Reduced motion: instant in / out

R8 — Dismiss behaviors

TriggerResult
Tap an action itemCloses menu, runs action
Tap a toggle itemToggles state, may stay open
Tap outside menuCloses menu, no action
Esc keyCloses menu
Scroll page (some surfaces)Closes menu
Resize windowRepositions 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

DensityItem heightPadding
Compact36 px12 px / 8 px
Default48 px16 px / 12 px
Comfortable56 px20 px / 16 px

R11 — Per-preset variation

PresetContainerItems
material34 px radius, 2 dp elevation, surface-containerTonal hover
material24 px radius, 8 dp shadow, white bgFilled hover
ios_cupertino14 px radius, blur backdropSolid pressed bg
gnome8 px radius, surface tonalSolid blue hover
windows_11Mica backdrop, 8 px radiusAccent bar on focus
brutalistSharp, thick black border, no shadowInverted hover
terminal_classicBordered 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 — error color)
  • themes/elevation.kmd — 2 dp tonal + shadow recipe
  • themes/color-roles.kmdsurface-container background
  • themes/typography.kmdbody-large for items, label-small for shortcuts
  • interaction/states.kmd — hover / focused / pressed layers
  • components/lists.kmd — sibling pattern (lists are persistent menus)
  • components/text-fields.kmd — exposed dropdown anchor
  • foundations/elements.kmd — Container + Control families

References