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
anduseMenuContentContext
hooks instead ofuseContext
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 contextconst NoResultsWarning = ({ searchValue }) => {const { hasVisibleItems } = useMenuContentContext();return searchValue && !hasVisibleItems ? (<Bannertype="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><MenuSearchInputid="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.
Name | Type | Default | Description | Required? |
---|---|---|---|---|
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.
Name | Type | Description |
---|---|---|
isOpen | boolean | Indicates if the menu is open. |
isListbox | boolean | Indicates if the menu is rendered as a listbox. |
items | TypeInternalItemProps[] | The array of menu items. |
itemsMap | { [key: string]: TypeInternalItemProps } | Map of item IDs to item objects. |
selectedItem | TypeInternalItemProps | null | The currently selected item (single select). |
selectedItems | TypeInternalItemProps[] | The currently selected items (multi-select). |
selectedItemIds | { [key: string]: boolean } | Map of selected item IDs. |
highlightedIndex | number | The index of the currently highlighted item (for keyboard navigation). |
isItemSelected(id) | (id: string) => boolean | Returns true if the item with the given id is selected. |
getToggleProps | function | Returns props to spread onto your custom toggle element. |
openMenu / closeMenu | () => void | Functions to programmatically open or close the menu. |
hasVisibleItems | boolean | Indicates if any menu items are currently visible (useful for filtering/search). |
getInputComponentProps | function | Returns props for custom input components (advanced use). |
contentRef | React.RefObject<HTMLUListElement> | Ref to the menu content element (advanced use). |
autoCloseOnEmpty | boolean | If true, the menu will automatically close when there are no visible items. |
isInitialized | boolean | Indicates if the menu context has been initialized. |