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:
| Prop | Type | Default | Description |
|---|---|---|---|
items | ActionsDropdownItem[] | [] | The list of action items |
ActionsDropdownItem
Each item extends IActionCard with one additional prop:
| Prop | Type | Description |
|---|---|---|
groupIndex | number | When this changes between consecutive items, a separator is rendered |
label | string | Item label |
icon | ReactNode | Optional icon |
description | string | Optional secondary text |
shortcut | string[] | Optional keyboard shortcut display |
onClick | () => void | Click handler |
variant | 'button' | 'switch' | Item mode |
checked | boolean | Switch state (switch mode) |
disabled | boolean | Disabled 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
onClickAwayor a parent key handler to close) - Tab — blocked within the list to keep focus inside the dropdown
Customization (slotProps)
| Slot | Type | Controls |
|---|---|---|
dropdown | Partial IDropdown | The underlying Dropdown |
list | HTMLUListElement props | The <ul> wrapper |
listItem | HTMLLIElement props | Each <li> item wrapper |
separator | HTMLSpanElement props | The 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).
Dropdown
A floating container positioned relative to an anchor element. The low-level primitive underlying ActionsDropdown — use when you need custom dropdown content.
Grid
A responsive CSS grid layout container. Five breakpoint-aware sizes map to standard column counts (4, 8, or 12) with horizontal margins.