Components · Overlays & menus
Dropdown
Dropdown menu component for contextual action menus and option lists. Distinct from Select (form input value selection) — Dropdown is for triggering actions.
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 "Dropdown" 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 `dropdown` spec and CORRECT every deviation — replace off-spec values, don't only add new ones.
- New component: build it to the `dropdown` 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
┌──────────────────┐
│ Label [▼] │ ← dropdown-trigger
└──────────────────┘
↓ (open)
┌──────────────────────┐
│ Section Label │ ← dropdown-section-label
│ ───────────────── │ ← dropdown-separator
│ [icon] Item 1 │ ← dropdown-item (see zetta-dropdown-item-v1_6.md)
│ [icon] Item 2 │
│ ───────────────── │
│ [icon] Danger item │ ← dropdown-item-danger
└──────────────────────┘ ← dropdown-panel
When to use
- Row-level action menus in tables (edit, duplicate, delete).
- Toolbar overflow menus.
- Contextual options related to a specific element.
When not to use
- When only one action is available — use a Button.
- When the user is selecting a form value — use Select.
- When filtering a dataset — use a Filter component with checkboxes.
Do
- Group related items with section labels and separators.
- Place destructive items (delete, remove) at the bottom, separated from safe actions.
- Keep item labels to 1–4 words.
Don't
- Don't use a dropdown for navigation — use Links or the Sidebar.
- Don't nest dropdowns inside dropdown panels.
- Don't use icons alone without text labels in dropdown items.
- Section labels: short noun. "Actions", "Export as", "More options". Title case.
- Item labels: verb phrase. "Edit record", "Export as CSV", "Delete permanently".
- Danger items: be specific. "Delete project" not "Delete".
- Max 10 items before grouping or pagination. More than 15 — use a dialog.
- Keyboard shortcut hints: right-aligned in muted text. "⌘K", "Del".
Keyboard
| Key | Behavior |
|---|---|
Enter / Space |
Open dropdown when trigger is focused |
Down Arrow |
Move focus to first item when panel opens |
Up / Down Arrow |
Navigate between items |
Home / End |
Jump to first / last item |
Enter |
Activate focused item |
Escape |
Close panel, return focus to trigger |
Tab |
Close panel, move focus to next focusable element |
Accessibility
- Trigger ARIA:
aria-haspopup="menu",aria-expanded="true/false",aria-controls="panel-id" - Panel ARIA:
role="menu",id="panel-id" - Items ARIA:
role="menuitem"(standard),role="menuitemcheckbox"(checkbox variant),role="menuitemradio"(radio variant) - Focus returns to the trigger when the panel closes.
- Disabled items:
aria-disabled="true"— still focusable but not activatable.
Spec
Values and token references resolved straight from the Zetta spec.
dropdown-trigger
- backgroundColor
- transparent
- textColor
- {colors.ink}
- fontFamily
- Geist
- fontSize
- 14px
- fontWeight
- 500
- borderColor
- {colors.border-strong}
- borderWidth
- 1px
- borderRadius
- {rounded.base}
- paddingX
- {spacing.sm}
- paddingY
- {spacing.xs}
- display
- inline-flex
- alignItems
- center
- gap
- {spacing.xs}
- cursor
- pointer
- height
- 32px
dropdown-trigger-hover
- backgroundColor
- {colors.surface-muted}
- borderColor
- {colors.border-strong}
dropdown-trigger-focus
- outline
- 2px solid {colors.border-focus}
- outlineOffset
- 2px
dropdown-trigger-open
- backgroundColor
- {colors.primary-bg}
- textColor
- {colors.primary}
- borderColor
- {colors.primary}
dropdown-trigger-disabled
- textColor
- {colors.disabled-text}
- borderColor
- {colors.disabled-border}
- backgroundColor
- {colors.disabled-bg}
- cursor
- not-allowed
dropdown-panel
- backgroundColor
- {colors.surface-card}
- borderColor
- {colors.hairline}
- borderWidth
- 1px
- borderRadius
- {rounded.base}
- shadow
- {elevation.shadow.overlay}
- padding
- {spacing.xs}
- minWidth
- 160px
- maxWidth
- 280px
- zIndex
- {elevation.z-index.popup}
dropdown-section-label
- textColor
- {colors.placeholder}
- fontSize
- 11px
- fontWeight
- 600
- fontFamily
- Geist
- textTransform
- uppercase
- letterSpacing
- 0.06em
- paddingX
- {spacing.sm}
- paddingY
- {spacing.xs}
dropdown-separator
- borderColor
- {colors.hairline}
- borderWidth
- 1px
- marginX
- {spacing.xs}
- marginY
- {spacing.xs}