Composition

Menu

Dropdown list of actions opened from a trigger: account menus, overflow ("more") menus, row actions

Markdown version for agents

Import
import { Menu } from '@amezquita/design-system/components/composition/Menu'

Live render

    Props

    PropTypeDescription
    triggerReact.ReactElementThe element that opens the menu. Must be a single element that forwards its ref — Button does. React.Fragment is not supported (Radix asChild).
    groups{ label?: string; items: { id: string; label: string; onSelect?: () => void; disabled?: boolean; variant?: 'default' | 'destructive'; icon?: React.ReactNode }[] }[]Items in groups; a separator is drawn between groups. One group with no label is a plain list.
    align?'start' | 'center' | 'end'
    side?'top' | 'right' | 'bottom' | 'left'
    open?boolean
    defaultOpen?boolean
    onOpenChange?(open: boolean) => void
    aria-label?stringAccessible name for the menu itself, when the trigger's label isn't enough context.
    className?string

    Usage example

    tsx
    <Menu
      trigger={<Button variant="secondary">Actions</Button>}
      groups={[
        {
          items: [
            { id: 'edit', label: 'Edit', onSelect: () => {} },
            { id: 'duplicate', label: 'Duplicate', onSelect: () => {} },
            { id: 'archive', label: 'Archive', disabled: true },
          ],
        },
        {
          items: [{ id: 'delete', label: 'Delete', variant: 'destructive', onSelect: () => {} }],
        },
      ]}
    />

    Accessibility

    • Built on Radix DropdownMenu: role="menu" / menuitem, the trigger gets aria-haspopup and aria-expanded
    • Keyboard: Enter, Space or ArrowDown opens and focuses the first item; arrow keys move; typing jumps to a matching item; Escape closes and returns focus to the trigger
    • An icon-only trigger needs an aria-label on the trigger itself
    • No z-index (decisions/0007): the panel is portalled, so it layers correctly inside a Dialog or Drawer (Menu.nesting.test.tsx)