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.io/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 |
Basic
<ActionsDropdown
anchorEl={anchorRef.current}
open={open}
placement="bottom-start"
offset={6}
onClickAway={() => setOpen(false)}
clickAwayIgnore={anchorRef.current ?? undefined}
items={[
{ icon: <PlusCircle />, label: 'New item', onClick: () => setOpen(false) },
{ icon: <PencilSimple />, label: 'Edit', onClick: () => setOpen(false) },
{ icon: <Trash />, label: 'Delete', onClick: () => setOpen(false) },
]}
/>With groups
items={[
{ groupIndex: 0, icon: <PlusCircle />, label: 'New item', onClick: () => setOpen(false) },
{ groupIndex: 0, icon: <PencilSimple />, label: 'Edit', onClick: () => setOpen(false) },
// separator rendered here (groupIndex changes: 0 → 1)
{ groupIndex: 1, icon: <Archive />, label: 'Archive', onClick: () => setOpen(false) },
{ groupIndex: 1, icon: <Trash />, label: 'Delete', onClick: () => setOpen(false) },
]}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.
Toggle switches
items={[
{
label: 'Compact view',
variant: 'switch',
checked: compact,
onClick: () => setCompact((v) => !v),
},
{
label: 'Notifications',
variant: 'switch',
checked: notifications,
onClick: () => setNotifications((v) => !v),
},
]}With keyboard shortcuts
items={[
{
label: 'New document',
shortcut: ['Meta', 'n'],
onClick: newDocument,
listen: true,
},
{
label: 'Search',
shortcut: ['Meta', 'k'],
onClick: openSearch,
listen: true,
},
]}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.
ItemNav
A "previous / N of X items / next" control with built-in ArrowUp/ArrowDown keyboard support — the item-navigation control used by Modal, reusable anywhere you browse a set of records.