Introducing PrimeReact v11-alpha 🎉Discover Now

useMenu

Hook that manages menu state, keyboard navigation, focus tracking, and submenu coordination.

basic-demo

Usage#

import { useMenu } from '@primereact/headless/menu';
import { useMenuSub } from '@primereact/headless/menu/sub';
 
const menu = useMenu();
const sub = useMenuSub({ defaultOpen: true });
const subTriggerProps = menu.getSubTriggerProps({ value: 'projects', sub });
 
return (
    <div {...menu.rootProps}>
        <ul {...menu.getListProps()}>
            <li {...menu.getItemProps({ value: 'item1' })}></li>
            <li {...sub.subProps}>
                <div {...subTriggerProps}></div>
                <ul {...menu.getListProps({ value: 'projects', sub })}></ul>
            </li>
        </ul>
    </div>
);

useMenu manages focus tracking, keyboard navigation, and submenu orchestration for menu structures — see Primitive for a component-based API.

Features#

  • Returns spread-ready prop objects: rootProps, triggerProps, labelProps, separatorProps, radioGroupProps
  • Dynamic prop getters: getItemProps, getSubTriggerProps, getListProps, getIndicatorProps
  • Built-in character search to jump to items by typing
  • Composite mode for nested menus with hover-to-open submenu behavior
  • Portal-based popup rendering with configurable positioning via usePopover
  • Checkbox and radio item types with getItemProps({ type: 'checkbox' | 'radio' })

Behavior#

Inline Menu#

By default, useMenu renders an inline menu. Spread rootProps on the container and getListProps() on the list element.

const menu = useMenu();

Portal (Popup) Menu#

When the popover is used, spread triggerProps on a button to toggle visibility. Use state.open to conditionally render the popup.

const menu = useMenu();
 
<button {...menu.triggerProps}>Open</button>;
{
    menu.state.open && <ul {...menu.getListProps()}>...</ul>;
}

Composite Mode#

Set composite to enable nested submenu behavior with hover-to-open and arrow key navigation across submenu levels.

const menu = useMenu({ composite: true });

Use useMenuSub for each submenu. Pass the sub instance to getSubTriggerProps and getListProps to wire up open/close behavior.

const sub = useMenuSub();
const subTriggerProps = menu.getSubTriggerProps({ value: 'files', sub });
const subListProps = menu.getListProps({ value: 'files', sub });

Checkbox and Radio Items#

Pass type and checked to getItemProps for checkbox or radio behavior. Wrap radio items in an element with radioGroupProps.

menu.getItemProps({ value: 'notifications', type: 'checkbox', checked: true });
menu.getItemProps({ value: 'light', type: 'radio', checked: selectedTheme === 'light' });

Indicator Props#

getIndicatorProps returns visibility state and data attributes for submenu arrows, checkmarks, or radio dots. Use match to control when the indicator is visible.

const indicatorProps = menu.getIndicatorProps({ type: 'checkbox', checked: true, match: 'checked' });
// indicatorProps.matchVisible === true

Controlled Visibility#

Use open and onOpenChange for programmatic control over the popup state.

const [open, setOpen] = React.useState(false);
const menu = useMenu({ open, onOpenChange: (e) => setOpen(e.value) });

Custom Styling with Data Attributes#

All prop getters attach data-scope="menu" and a data-part attribute for CSS targeting. Additional data attributes reflect runtime state:

getItemProps — data-part="item", plus data-value, data-focused (keyboard/mouse focus), data-disabled, and data-checked (for checkbox/radio items).

getSubTriggerProps — data-part="subtrigger", plus data-value, data-focused, data-disabled, and data-open (when its submenu is expanded).

getIndicatorProps — data-part="indicator", plus data-open/data-closed (submenu indicators) or data-checked/data-unchecked (checkbox/radio indicators).

getListProps — data-part="list".

[data-scope='menu'][data-part='item'][data-focused] {
    background-color: var(--surface-hover);
}
 
[data-scope='menu'][data-part='item'][data-checked] {
    font-weight: 600;
}
 
[data-scope='menu'][data-part='subtrigger'][data-open] {
    background-color: var(--surface-hover);
}
 
[data-scope='menu'][data-part='indicator'][data-checked] {
    color: var(--primary);
}
 
[data-scope='menu'][data-part='item'][data-disabled] {
    opacity: 0.6;
    pointer-events: none;
}

API#

useMenu#

NameTypeDefault
openboolean—
Controlled open state of the menu.
defaultOpenbooleanfalse
Default open state for uncontrolled mode.
compositebooleanfalse
Whether the menu is in composite mode (menubar). In composite mode, submenus open on hover and focusedOptionId is an array tracking the focus path.
appendTo"body" | HTMLElement | "self"'body'
The element to which the overlay is appended.
baseZIndexnumber0
Base zIndex value to use in layering.
autoZIndexbooleantrue
Whether to automatically manage layering.
tabIndexnumber0
Index of the element in tabbing order.
onOpenChange(event: useMenuOpenChangeEvent) => void—
Callback to invoke when the open state changes.

useMenuSub#

NameTypeDefault
openboolean—
Controlled open state of the submenu.
defaultOpenbooleanfalse
Default open state for uncontrolled mode.
disabledbooleanfalse
Whether the submenu is disabled.
onOpenChange(event: useMenuSubOpenChangeEvent) => void—
Callback to invoke when the open state changes.

Accessibility#

See Menu Primitive for WAI-ARIA compliance details and keyboard support.