Headless Core — Menu List
Powers Menu List internally. Unlike Dropdown and Context Menu, this factory has no open/close state of its own — it’s just the focusable item list, often used inside one of those two for the actual panel content.
import { createMenuList } from '@andersseen/headless-components/menu-list';
const menu = createMenuList({ ariaLabel: 'File actions', items: [ { id: 'edit', label: 'Edit' }, { separator: true }, { id: 'delete', label: 'Delete', intent: 'destructive' }, { id: 'locked', label: 'Locked', disabled: true }, ], onSelect: id => console.log('Selected:', id),});
const menuProps = menu.getMenuProps();const itemProps = menu.getItemProps(item, index);Config
Section titled “Config”| Option | Type | Default | Notes |
|---|---|---|---|
ariaLabel | string | 'Menu' | |
items | MenuItemConfig[] | [] | { id?, label?, shortcut?, icon?, separator?, intent?, disabled? }. |
onSelect | (itemId: string) => void | — | |
rovingFocus | boolean | true | See Prop-getters below. |
An item is only interactive (selectable/focusable) if separator is falsy
and it has a non-empty id — items without an id render but are skipped by
keyboard navigation and queries.getInteractiveItems().
items, ariaLabel, focusedIndex (-1 = none — an index into items,
including separators, not into the interactive subset).
Actions & queries
Section titled “Actions & queries”| Member | Signature | Notes |
|---|---|---|
actions.setItems(items) | (items: MenuItemConfig[]) => void | Resets focusedIndex to -1. |
actions.focusItem(index) | (index: number) => void | No-ops for a separator or disabled item. |
actions.selectItem(itemId) | (itemId: string) => void | No-ops for a disabled/non-interactive item; doesn’t move focus itself. |
queries.getInteractiveItems() | () => MenuInteractiveItem[] | Filters out separators, items without id, and disabled items. |
queries.getInteractiveItemIds() | () => string[] | |
queries.getItemIndex(templateIndex) | (templateIndex: number) => number | Maps a raw items index to its position among interactive items only — useful if your rendering layer needs a “1 of 3” style label. |
Prop-getters & handlers
Section titled “Prop-getters & handlers”| Getter | Returns |
|---|---|
getMenuProps() | role: 'menu', aria-label |
getItemProps(item, index) | role: 'menuitem', tabindex, aria-disabled, data-state, data-disabled |
getSeparatorProps() | role: 'separator' |
With rovingFocus: true (the default), only the currently-focused item gets
tabindex: 0 — the rest are -1, so Tab moves focus into and out of the
whole menu in one step, and arrow keys move focusedIndex within it. With
rovingFocus: false, every enabled item gets tabindex: 0 individually (the
browser’s natural Tab order visits each one).
handleMenuKeyDown(event) — ArrowDown/ArrowUp move the focus, Home/End
jump to the first/last interactive item. handleItemKeyDown(event, item) —
Enter/Space select the item; everything else delegates to
handleMenuKeyDown, so you only need to attach this one handler to each item
element.
Live example
Section titled “Live example”Next steps
Section titled “Next steps”Dropdown and Context Menu — the two disclosure factories this list is typically rendered inside.