Components · Overlays & menus
Menu
A vertical list of navigation items or commands on a popover surface — used standalone (sidebar, settings list) or inside a popover. Distinct from a button-triggered Dropdown. Supports sections, separators, shortcuts, a selected item, and destructive items.
Preview
Build with the skill
No package to install — hand this to your AI to generate or migrate the component in your stack.
Apply the Zetta "Menu" component to this project using the zetta-design-md skill.
WHEN TO USE
- Migrating or fixing existing code (the common case): audit the current implementation against the `menu` spec (variants: item, section, separator, danger) and CORRECT every deviation — replace off-spec values, don't only add new ones.
- New component: build it to the `menu` spec in this project's stack.
AUDIT & FIX (when the component already exists)
Compare it to the skill's spec and fix, don't just append: hardcoded colors/hex, off-scale radius, wrong fonts, dark/navy fills, shadows used in place of borders (or missing borders), brand lime used as a CTA or interaction (must be primary indigo), and any missing or wrong hover / focus / active / disabled / invalid state or missing 2px focus ring. Replace each with the correct Zetta token.
ALWAYS
- Pull the authoritative spec and tokens from the skill — never guess or hardcode values.
- Use this project's own component conventions and framework idioms.
- Honor the Zetta guardrails (brand = identity only, primary = interaction, shadows for overlays) and verify in Light, Dark, and Accessibility. Anatomy & rules
╭─────────────────────────────╮
│ SECTION TITLE │ ← menu-section-title
│ ─────────────────────────── │ ← menu-separator
│ 🏠 Home │ ← menu-item
│ ★ Starred ⌘K │ ← menu-item with shortcut
│ ─────────────────────────── │
│ 🗑 Delete │ ← menu-item-danger
╰─────────────────────────────╯
| State | Visual |
|---|---|
| Closed | Trigger at rest |
| Open | Panel visible, items listed |
| Item hover | {colors.surface-muted} fill on hovered item |
| Item focus | {colors.surface-muted} fill + focus outline on item |
| Item selected | {colors.primary-bg} fill, check icon prefix |
| Item disabled | {colors.disabled-text}, not-allowed cursor |
| Danger item hover | {colors.danger-bg} fill, {colors.danger} text |
When to use
- Sidebar navigation item lists.
- Context menus (right-click or kebab-menu triggered).
- Settings or preference panels with a list of options.
- Command palette result lists.
When not to use
- A single list of actions triggered by a button — use Dropdown.
- Tab-like navigation between panels — use Tabs.
Do
- Group related items with
menu-sectionand a short section title. - Use
menu-separatorbetween unrelated groups. - Show keyboard shortcuts with
menu-item-shortcutwhen available. - Use
menu-item-dangeronly for destructive, irreversible actions.
Don't
- Don't nest menus more than one level deep in a popover.
- Don't put more than 10 items in a single section without grouping.
- Item labels: noun or verb phrase, title case. "Project Settings", "Delete Project".
- Section titles: uppercase, 1–2 words. "WORKSPACE", "RECENT".
- Keyboard shortcuts: display with system-appropriate modifier (⌘ on Mac, Ctrl on Windows).
Keyboard
| Key | Behavior |
|---|---|
Tab |
Move focus to the menu trigger |
Enter / Space |
Open the menu |
Up / Down Arrow |
Navigate between menu items |
Home / End |
Jump to first / last item |
Enter |
Activate the focused item |
Escape |
Close the menu, return focus to trigger |
Accessibility
- ARIA role:
role="menu"on container;role="menuitem"on each item. - Selected item:
aria-checked="true"for checkbox-style selection;aria-current="page"for navigation. - Disabled items:
aria-disabled="true"— keep in DOM and tab order so screen readers can announce them. - Sections:
role="group"witharia-labelledbypointing to the section title.
Spec
Values and token references resolved straight from the Zetta spec.
menu
- backgroundColor
- {colors.popover}
- borderColor
- {colors.hairline}
- borderWidth
- 1px
- borderRadius
- {rounded.base}
- shadow
- {elevation.shadow.overlay}
- padding
- {spacing.xs}
- minWidth
- 200px
menu-section
- paddingY
- {spacing.xs}
menu-section-title
- fontFamily
- Geist
- fontSize
- 11px
- fontWeight
- 500
- textColor
- {colors.muted}
- textTransform
- uppercase
- letterSpacing
- 0.06em
- paddingX
- {spacing.sm}
- paddingY
- {spacing.xs}
menu-separator
- height
- 1px
- backgroundColor
- {colors.hairline}
- marginY
- {spacing.xs}
menu-item
- display
- flex
- alignItems
- center
- gap
- {spacing.sm}
- paddingX
- {spacing.sm}
- paddingY
- {spacing.xs}
- borderRadius
- {rounded.sm}
- fontFamily
- Geist
- fontSize
- 14px
- fontWeight
- 400
- lineHeight
- 1.40
- textColor
- {colors.ink}
- iconColor
- {colors.muted}
- iconSize
- {icons.sizeMap.md}
- cursor
- pointer
- minHeight
- 32px
menu-item-hover
- backgroundColor
- {colors.primary-bg}
- textColor
- {colors.primary}
menu-item-active
- backgroundColor
- {colors.surface-pressed}
menu-item-selected
- backgroundColor
- {colors.primary-bg}
- textColor
- {colors.primary}
- iconColor
- {colors.primary}
- fontWeight
- 500
menu-item-danger
- textColor
- {colors.danger}
- iconColor
- {colors.danger}
menu-item-danger-hover
- backgroundColor
- {colors.danger-bg}
menu-item-disabled
- textColor
- {colors.disabled-text}
- iconColor
- {colors.disabled-text}
- cursor
- not-allowed
menu-item-focus
- outline
- 2px solid {colors.border-focus}
- outlineOffset
- -2px
menu-item-shortcut
- marginLeft
- auto
- fontSize
- 12px
- fontFamily
- Geist Mono
- textColor
- {colors.muted}