UnflowUI
Components

ActionsDropdown

An opinionated dropdown populated from an items array. Handles grouping, ActionCard rendering, and keyboard navigation automatically.

Overview

ActionsDropdown is the opinionated layer on top of Dropdown. You pass an array of items and it takes care of rendering each item as an ActionCard, managing groups/separators via groupIndex, and wiring up useListNavigation for arrow-key navigation.

Import

import { ActionsDropdown } from '@unflow/ui/components/ActionsDropdown';

Props

All Dropdown props are accepted (positioning, visibility, click-away, etc.), plus:

PropTypeDefaultDescription
itemsActionsDropdownItem[][]The list of action items

ActionsDropdownItem

Each item extends IActionCard with one additional prop:

PropTypeDescription
groupIndexnumberWhen this changes between consecutive items, a separator is rendered
labelstringItem label
iconReactNodeOptional icon
descriptionstringOptional secondary text
shortcutstring[]Optional keyboard shortcut display
onClick() => voidClick handler
variant'button' | 'switch'Item mode
checkedbooleanSwitch state (switch mode)
disabledbooleanDisabled state

Examples

Basic action list

<ActionsDropdown
  anchorEl={anchor}
  open={isOpen}
  onClickAway={() => setOpen(false)}
  clickAwayIgnore={anchor ?? undefined}
  items={[
    { label: 'Edit', icon: <PencilIcon />, onClick: () => edit() },
    { label: 'Duplicate', icon: <CopyIcon />, onClick: () => duplicate() },
    { label: 'Delete', icon: <TrashIcon />, onClick: () => remove(), variant: 'button' },
  ]}
/>

With groups

items={[
  { label: 'Edit', groupIndex: 0, onClick: edit },
  { label: 'Rename', groupIndex: 0, onClick: rename },
  // separator rendered here (groupIndex changes: 0 → 1)
  { label: 'Delete', groupIndex: 1, onClick: remove },
]}

A separator <span> is rendered whenever groupIndex changes between consecutive items. You don't need to interleave separator objects — just assign the same groupIndex to items that belong together.

With keyboard shortcuts

items={[
  {
    label: 'New document',
    shortcut: ['Meta', 'n'],
    onClick: newDocument,
    listen: true,
  },
  {
    label: 'Search',
    shortcut: ['Meta', 'k'],
    onClick: openSearch,
    listen: true,
  },
]}

Toggle switches

const [compact, setCompact] = useState(false);

items={[
  {
    label: 'Compact view',
    variant: 'switch',
    checked: compact,
    onClick: () => setCompact(c => !c),
  },
]}

Keyboard navigation

ActionsDropdown uses useListNavigation internally:

  • Arrow Down / Up — move focus between items
  • Enter — trigger the focused item's onClick
  • Escape — propagates up (not handled internally; use onClickAway or a parent key handler to close)
  • Tab — blocked within the list to keep focus inside the dropdown

Customization (slotProps)

SlotTypeControls
dropdownPartial IDropdownThe underlying Dropdown
listHTMLUListElement propsThe <ul> wrapper
listItemHTMLLIElement propsEach <li> item wrapper
separatorHTMLSpanElement propsThe group separator element
<ActionsDropdown
  anchorEl={anchor}
  open={isOpen}
  items={items}
  slotProps={{
    list: { className: 'min-w-48' },
    separator: { className: 'my-1' },
  }}
/>

Accessibility

Each ActionCard inside the list receives tabIndex and data-navigation-item attributes from useListNavigation. The list container manages keyboard focus so that only one item is in the tab order at a time (roving tabindex pattern).