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:
- ids- Partial<{ 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.
- defaultHighlightedValue- stringThe initial highlighted value of the menu item when rendered. Use when you don't need to control the highlighted value of the menu item.
- highlightedValue- stringThe 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.
- anchorPoint- PointThe positioning point for the menu. Can be set by the context menu trigger or the button trigger.
- loopFocus- booleanWhether to loop the keyboard navigation.
- positioning- PositioningOptionsThe options used to dynamically position the menu
- closeOnSelect- booleanWhether to close the menu when an option is selected
- aria-label- stringThe accessibility label for the menu
- open- booleanThe controlled open state of the menu
- onOpenChange- (details: OpenChangeDetails) => voidFunction called when the menu opens or closes
- defaultOpen- booleanThe initial open state of the menu when rendered. Use when you don't need to control the open state of the menu.
- typeahead- booleanWhether the pressing printable characters should trigger typeahead navigation
- composite- booleanWhether the menu is a composed with other composite widgets like a combobox or tabs
- navigate- (details: NavigateDetails) => voidFunction to navigate to the selected item if it's an anchor element
- dir- "ltr" | "rtl"The document's text/writing direction.
- id- stringThe 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 pressed
- onRequestDismiss- (event: LayerDismissEvent) => voidFunction called when this layer is closed due to a parent layer being closed
- onPointerDownOutside- (event: PointerDownOutsideEvent) => voidFunction called when the pointer is pressed down outside the component
- onFocusOutside- (event: FocusOutsideEvent) => voidFunction called when the focus is moved outside the component
- onInteractOutside- (event: InteractOutsideEvent) => voidFunction called when an interaction happens outside the component
Machine API
The menu api exposes the following methods:
- open- booleanWhether the menu is open
- setOpen- (open: boolean) => voidFunction to open or close the menu
- highlightedValue- stringThe id of the currently highlighted menuitem
- setHighlightedValue- (value: string) => voidFunction to set the highlighted menuitem
- setParent- (parent: ParentMenuService) => voidFunction to register a parent menu. This is used for submenus
- setChild- (child: ChildMenuService) => voidFunction to register a child menu. This is used for submenus
- reposition- (options?: Partial<PositioningOptions>) => voidFunction to reposition the popover
- getOptionItemState- (props: OptionItemProps) => OptionItemStateReturns the state of the option item
- getItemState- (props: ItemProps) => ItemStateReturns the state of the menu item
- addItemListener- (props: ItemListenerProps) => VoidFunctionSetup the custom event listener for item selection event
Data Attributes
CSS Variables
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.