Nested Menu
An accessible dropdown and context menu that is used to display a list of actions or options that a user can choose.
Features
- Support for items, labels, groups of items
- Focus is fully managed using
aria-activedescendantpattern - Typeahead to allow focusing items by typing text
- Keyboard navigation support including arrow keys, home/end, page up/down
Installation
To use the menu machine in your project, run the following command in your command line:
Anatomy
To set up the menu correctly, you'll need to understand its anatomy and how we name its parts.
Each part includes a
data-partattribute to help identify them in the DOM.
Usage
First, import the menu package into your project
import * as menu from "@zag-js/menu"
The menu package exports two key functions:
machine— The state machine logic for the menu widget.connect— The function that translates the machine's state to JSX attributes and event handlers.
You'll need to provide a unique
idto theuseMachinehook. This is used to ensure that every part has a unique identifier.
Next, import the required hooks and functions for your framework and use the menu machine in your project 🔥
- Destructure the machine's service returned from the
useMachinehook. - Use the exposed
setParentandsetChildfunctions provided by the menu's connect function to assign the parent and child menus respectively. - Create trigger item's using the
api.getTriggerItemProps(...)function.
When building nested menus, you'll need to use:
setParent(...)— Function to register a parent menu's machine in the child menu's context.setChild(...)— Function to register a child menu's machine in the parent menu's context.
Styling guide
Earlier, we mentioned that each menu part has a data-part attribute added to
them to select and style them in the DOM.
Highlighted item state
When an item is highlighted, via keyboard navigation or pointer, it is given a
data-highlighted attribute.
[data-part="item"][data-highlighted] { /* styles for highlighted state */ } [data-part="item"][data-type="radio|checkbox"][data-highlighted] { /* styles for highlighted state */ }
Disabled item state
When an item or an option item is disabled, it is given a data-disabled
attribute.
[data-part="item"][data-disabled] { /* styles for disabled state */ } [data-part="item"][data-type="radio|checkbox"][data-disabled] { /* styles for disabled state */ }
Using arrows
When using arrows within the menu, you can style it using css variables.
[data-part="arrow"] { --arrow-size: 20px; --arrow-background: red; }
Checked option item state
When an option item is checked, it is given a data-state attribute.
[data-part="item"][data-type="radio|checkbox"][data-state="checked"] { /* styles for checked state */ }
Methods and Properties
Machine Context
The menu machine exposes the following context properties:
idsPartial<{ trigger: string; contextTrigger: string; content: string; groupLabel: (id: string) => string; group: (id: string) => string; positioner: string; arrow: string; }>The ids of the elements in the menu. Useful for composition.defaultHighlightedValuestringThe initial highlighted value of the menu item when rendered. Use when you don't need to control the highlighted value of the menu item.highlightedValuestringThe controlled highlighted value of the menu item.onHighlightChange(details: HighlightChangeDetails) => voidFunction called when the highlighted menu item changes.onSelect(details: SelectionDetails) => voidFunction called when a menu item is selected.anchorPointPointThe positioning point for the menu. Can be set by the context menu trigger or the button trigger.loopFocusbooleanWhether to loop the keyboard navigation.positioningPositioningOptionsThe options used to dynamically position the menucloseOnSelectbooleanWhether to close the menu when an option is selectedaria-labelstringThe accessibility label for the menuopenbooleanThe controlled open state of the menuonOpenChange(details: OpenChangeDetails) => voidFunction called when the menu opens or closesdefaultOpenbooleanThe initial open state of the menu when rendered. Use when you don't need to control the open state of the menu.typeaheadbooleanWhether the pressing printable characters should trigger typeahead navigationcompositebooleanWhether the menu is a composed with other composite widgets like a combobox or tabsnavigate(details: NavigateDetails) => voidFunction to navigate to the selected item if it's an anchor elementdir"ltr" | "rtl"The document's text/writing direction.idstringThe unique identifier of the machine.getRootNode() => ShadowRoot | Node | DocumentA root node to correctly resolve document in custom environments. E.x.: Iframes, Electron.onEscapeKeyDown(event: KeyboardEvent) => voidFunction called when the escape key is pressedonPointerDownOutside(event: PointerDownOutsideEvent) => voidFunction called when the pointer is pressed down outside the componentonFocusOutside(event: FocusOutsideEvent) => voidFunction called when the focus is moved outside the componentonInteractOutside(event: InteractOutsideEvent) => voidFunction called when an interaction happens outside the component
Machine API
The menu api exposes the following methods:
openbooleanWhether the menu is opensetOpen(open: boolean) => voidFunction to open or close the menuhighlightedValuestringThe id of the currently highlighted menuitemsetHighlightedValue(value: string) => voidFunction to set the highlighted menuitemsetParent(parent: MenuService) => voidFunction to register a parent menu. This is used for submenussetChild(child: MenuService) => voidFunction to register a child menu. This is used for submenusreposition(options?: Partial<PositioningOptions>) => voidFunction to reposition the popovergetOptionItemState(props: OptionItemProps) => OptionItemStateReturns the state of the option itemgetItemState(props: ItemProps) => ItemStateReturns the state of the menu itemaddItemListener(props: ItemListenerProps) => VoidFunctionSetup the custom event listener for item selection event
Data Attributes
Accessibility
Uses aria-activedescendant pattern to manage focus movement among menu items.
Keyboard Interactions
- SpaceOpens/closes the nested menu.
- EnterOpens/closes the nested menu.
- ArrowDownMoves focus to the next item.
- ArrowUpMoves focus to the previous item.
- ArrowRightOpens the nested menu.
- ArrowLeftCloses the nested menu.
- EscCloses the nested menu and moves focus to the parent menu item.
Edit this page on GitHub