Components

Menu - New!

Menu is an ecosystem of components that provide a structured and accessible way to create menus.

What is the Menu Context?

The Menu Context is a React context provided by the Seeds menu system that acts as a shared data store for menu-related state and helpers. Rather than managing state itself, the context exposes state and actions that are primarily managed by Downshift, the underlying library that powers accessibility, keyboard navigation, and selection logic in Seeds menus. The context simply makes this state and these helpers available to any component within the menu tree, allowing for flexible composition and customization.

Why Menu Context is Important

Menu Context is important because it provides a consistent way for all menu components—such as toggles, items, headers, and groups—to access the current menu state and actions. This enables developers to build custom menu elements, display menu state elsewhere in the UI, or hook into menu events, all without having to manage the core menu logic themselves. The actual state management (open/close, selection, highlighting, etc.) is handled by Downshift, while the context acts as a bridge to share that state.

How to Use Menu Context

Accessing Menu Context

Use the useMenuToggleContext and useMenuContentContext hooks from @sproutsocial/seeds-react-menu instead of React’s useContext to safely access menu context.

  • These hooks include helpful error handling to ensure they are used within the correct provider.
  • useMenuToggleContext exposes toggle-specific context, which may not be present in the content context.
  • useMenuContentContext provides access to the shared menu state and helpers relevant to menu content and items.

Best Practices

  • Use the useMenuToggleContext and useMenuContentContext hooks instead of useContext for safer and more reliable access to menu context.
  • Use the toggle context for toggle-specific logic, and the content context for menu content and item logic.
  • Rely on the built-in menu components for most use cases; use the context directly only for advanced customization.
  • Remember that the context is a data store—actual state management is handled by Downshift.

Note: The MenuToggleContext and MenuContentContext are intentionally kept separate to avoid context collisions, especially in advanced scenarios such as submenus or nested menus. For example, when a toggle is nested inside a menu content (as in a submenu), each toggle and content pair can maintain its own independent context. This separation ensures that toggles and menu content do not accidentally share or overwrite each other's state.

Common Use Cases

Here are some typical scenarios where accessing the menu context is useful:

Custom Toggle Components

Use the useMenuToggleContext hook to customize the content of a MenuToggleButton based on the menu's open state.

import { MenuToggleButton, useMenuToggleContext } from '@sproutsocial/seeds-react-menu'
() => {
const CustomToggle = () => {
const { isOpen } = useMenuToggleContext();
return (
<MenuToggleButton>
{isOpen ? 'Close Menu' : 'Open Menu'}
</MenuToggleButton>
);
};
return (
<Box display="flex" justifyContent="center" alignItems="center" height="100%">
<ActionMenu menuToggleElement={<CustomToggle />}>
<MenuContent>
<MenuItem id="item-1">Item 1</MenuItem>
<MenuItem id="item-2">Item 2</MenuItem>
<MenuItem id="item-3">Item 3</MenuItem>
</MenuContent>
</ActionMenu>
</Box>
);
}

For advanced custom toggle patterns—such as building your own toggle element and handling menu state directly—see Using Custom Toggle Elements.

Displaying Menu State Outside the Menu

You can use the useMenuToggleContext hook to display multiple pieces of menu state outside the menu—without creating a custom toggle button.

import { useMenuToggleContext } from '@sproutsocial/seeds-react-menu'
() => {
const MenuStateDisplay = () => {
const { isOpen, highlightedIndex, selectedItem } = useMenuToggleContext();
return (
<Box mb={300}>
<div>Menu is {isOpen ? 'open' : 'closed'}</div>
<div>Highlighted index: {highlightedIndex ?? 'None'}</div>
<div>
Selected item: {selectedItem ? selectedItem.children || selectedItem.id : 'None'}
</div>
</Box>
);
};
return (
<Box display="flex" flexDirection="column" alignItems="center" height="100%">
<SingleSelectMenu menuToggleElement={
<>
<MenuStateDisplay />
<MenuToggleButton>Toggle Menu</MenuToggleButton>
</>
}>
<MenuContent>
<MenuItem id="item-1">Item 1</MenuItem>
<MenuItem id="item-2">Item 2</MenuItem>
<MenuItem id="item-3">Item 3</MenuItem>
</MenuContent>
</SingleSelectMenu>
</Box>
);
}

Note: This approach only works when your component is passed to menuToggleElement. To display these values elsewhere on the page, capture them with a callback and store them in state outside of the menu.

Displaying Selected Items in a Multi-Select Menu

You can use the useMenuContentContext hook to access the selected items and display them in a menu header above the menu items.

import { useMenuContentContext } from '@sproutsocial/seeds-react-menu'
() => {
const SelectedItemsDisplay = () => {
const { selectedItems } = useMenuContentContext();
const selectedLabels = selectedItems?.map(item => item.id).join(', ') || 'None';
return (
<MenuHeader>
Selected: {selectedLabels}
</MenuHeader>
);
};
return (
<Box display="flex" flexDirection="column" alignItems="center" height="100%">
<MultiSelectMenu menuToggleElement={<MenuToggleButton>Toggle Menu</MenuToggleButton>}>
<SelectedItemsDisplay />
<MenuContent>
<MenuItem id="item-1" inputType="checkbox">Item 1</MenuItem>
<MenuItem id="item-2" inputType="checkbox">Item 2</MenuItem>
<MenuItem id="item-3" inputType="checkbox">Item 3</MenuItem>
</MenuContent>
</MultiSelectMenu>
</Box>
);
}

Filter Menu with Search and No Results Warning

This example uses MenuSearchInput in the menu header and displays a warning banner below the input if there are no visible items and the user has entered a search value, using hasVisibleItems from the menu content context.

import { MenuSearchInput, MenuHeader, ActionMenu, MenuToggleButton, MenuContent, MenuGroup, MenuItem, Box, useMenuContentContext } from '@sproutsocial/seeds-react-menu'
() => {
const [searchValue, setSearchValue] = React.useState('');
const items = [
{ id: 'compose', label: 'Compose' },
{ id: 'messages', label: 'View Messages' },
{ id: 'analyze', label: 'Analyze post' }
];
// This component must be rendered inside MenuContent to access the context
const NoResultsWarning = ({ searchValue }) => {
const { hasVisibleItems } = useMenuContentContext();
return searchValue && !hasVisibleItems ? (
<Banner
type="warning"
text="Try something else"
style={{ marginTop: 16, marginBottom: 0 }}
/>
) : null;
};
return (
<Box display="flex" justifyContent="center" alignItems="center" height="100%">
<ActionMenu menuToggleElement={<MenuToggleButton>Open Menu</MenuToggleButton>}>
<MenuHeader>
<MenuSearchInput
id="action-search"
name="search"
type="search"
aria-label="Search Menu"
value={searchValue}
onChange={e => setSearchValue(e.target.value)}
/>
<NoResultsWarning searchValue={searchValue} />
</MenuHeader>
<MenuContent searchValue={searchValue}>
<MenuGroup id="actions">
{items.map(item => (
<MenuItem key={item.id} id={item.id} searchValue={searchValue}>
{item.label}
</MenuItem>
))}
</MenuGroup>
</MenuContent>
</ActionMenu>
</Box>
);
}

How the Contexts Work Together

Both MenuToggleContext and MenuContentContext derive their data from a shared internal hook, useMenu. This hook is responsible for accessing and providing the core menu state and helpers, which are primarily managed by Downshift. The parent menu component initializes this state and passes it to useMenu, which then supplies the data to the context providers in MenuRoot. This ensures that both the toggle and content contexts are always in sync and accurately reflect the current menu state.

This design allows different parts of the menu (such as toggles and content) to access the same underlying state, while still keeping their contexts separate to avoid collisions in nested or complex menu structures.

About MenuRoot

Most Seeds menus use MenuRoot internally to set up the menu context providers and the shared hook that drives menu state. In typical usage, you won’t need to interact with MenuRoot directly—it's handled for you by the menu components.

MenuRoot is primarily useful for extremely advanced use cases. For example, our NestedMenu component overrides the default MenuRoot to enable advanced behaviors like sliding transitions between nested menus. Unless you are building highly customized or deeply nested menu structures, you will likely never need to use or override MenuRoot yourself.

NameTypeDefaultDescriptionRequired?
menuToggleElement
React.ReactElement<any>
The element that is or contains an element that will toggle the menu when clicked. May contain labels, buttons, inputs or other content as needed.
popoutProps
Partial< Omit<TypePopoutProps, "isOpen" | "setIsOpen" | "content" | "children"> >
{}
Props to pass through to the underlying Popout. Any Popout prop is valid except those explicitly omitted.
width
TypePopoutProps["width"]
The width of the Popout content. Defaults to 200px or the width of the toggle element, whichever is greater.

MenuRoot also optionally accepts all menu context props and children to display in the menu while open

API Reference

The following table lists the most commonly used values and methods available from the menu context. For the full API and advanced usage, see the MenuContext source file on GitHub.

NameTypeDescription
isOpenbooleanIndicates if the menu is open.
isListboxbooleanIndicates if the menu is rendered as a listbox.
itemsTypeInternalItemProps[]The array of menu items.
itemsMap{ [key: string]: TypeInternalItemProps }Map of item IDs to item objects.
selectedItemTypeInternalItemProps | nullThe currently selected item (single select).
selectedItemsTypeInternalItemProps[]The currently selected items (multi-select).
selectedItemIds{ [key: string]: boolean }Map of selected item IDs.
highlightedIndexnumberThe index of the currently highlighted item (for keyboard navigation).
isItemSelected(id)(id: string) => booleanReturns true if the item with the given id is selected.
getTogglePropsfunctionReturns props to spread onto your custom toggle element.
openMenu / closeMenu() => voidFunctions to programmatically open or close the menu.
hasVisibleItemsbooleanIndicates if any menu items are currently visible (useful for filtering/search).
getInputComponentPropsfunctionReturns props for custom input components (advanced use).
contentRefReact.RefObject<HTMLUListElement>Ref to the menu content element (advanced use).
autoCloseOnEmptybooleanIf true, the menu will automatically close when there are no visible items.
isInitializedbooleanIndicates if the menu context has been initialized.

Further Reading