Components
Nested Menu
A Nested Menus allows users to navigate grouped or hierarchical options within a single Popout.
Unlike traditional submenus that appear progressively across multiple layers, all nested levels are contained and managed within one menu surface. This approach keeps the experience compact and easier to scan, while still supporting complex option structures.
import { NestedMenu } from '@sproutsocial/seeds-react-menu'
() => {const menus = {root: {MenuComponent: ActionMenu,menuProps: {menuItems: [{id: "parent-1",children: "Open inner menu",childMenuId: "inner",},{id: "other-root",children: "No child menu",},],},MenuHeaderComponent: null,},inner: {MenuComponent: ActionMenu,menuProps: {children: (<NestedMenuContent><NestedMenuItemid="item-2"children="Books as ActionMenu"childMenuId="books-action"/><NestedMenuItemid="item-3"children="Books as MultiSelect"childMenuId="books-multiselect"/><NestedMenuItemid="item-4"children="Books as SingleSelect"childMenuId="books-select"/></NestedMenuContent>),},menuHeaderProps: {title: "Inner Menu",},},"books-action": {MenuComponent: ActionMenu,menuProps: {menuItems: TEST_BOOKS.map((book) => ({id: book.id,children: book.title,childMenuId: "edit-book",})),},menuHeaderProps: {title: "Manage Books",},},"books-multiselect": {MenuComponent: MultiSelectMenu,menuProps: {menuItems: TEST_BOOKS.map((book) => ({id: book.id,children: book.title,})),},menuHeaderProps: {title: "Select Books",},},"books-select": {MenuComponent: SingleSelectMenu,menuProps: {menuItems: TEST_BOOKS.map((book) => ({id: book.id,children: book.title,})),},menuHeaderProps: {title: "Select One Book",},},"edit-book": {MenuComponent: ActionMenu,menuProps: {menuItems: [{id: "archive",children: "Archive",},{id: "delete",children: "Delete",},],},menuHeaderProps: {title: "Edit Book",backArrowLabel: "Back to Books",},},};return (<Box display="flex" justifyContent="center" alignItems="center" height="100%"><NestedMenuinitialMenuId="root"menus={menus}backArrowLabel="Go Back"menuToggleElement={<MenuToggleButton>Open Menu</MenuToggleButton>}/></Box>)}
Properties
Name | Type | Default | Description | Required? |
---|---|---|---|---|
initialMenuId | string | The id of the menu to display when the NestedMenu is opened. | ||
onBackButtonClick | (menuId: string) => void | An optional callback to fire when the back button is clicked. | ||
backArrowLabel | string | Sets the default localized label for the back arrow in all child menus.
This label can be overridden by including backArrowLabel in a menu's menuHeaderProps. | ||
menus | {
[id: string]:
| TypeNestedMenuMapProps<TypeActionMenuProps<I>, I>
| TypeNestedMenuMapProps<TypeSingleSelectMenuProps<I>, I>
| TypeNestedMenuMapProps<TypeMultiSelectMenuProps<I>, I>;
} | An object of the menus and child menus to be displayed, keyed by id. |
NestedMenu Components
The following components are provided to help you build robust, accessible nested menus:
- NestedMenu – The main component for rendering a multi-level menu.
- NestedMenuHeader – Used for menu headers and navigation.
- NestedMenuItem – Used for items that open submenus.
- NestedMenuContent – Used to wrap menu items for consistent styling and keyboard navigation.
Menu Header
A menu header provides context and navigation for each menu level.
It can display a title, a back arrow for navigation, and custom content.
- Use
menuHeaderProps
to pass props to the default header. - To provide a custom header, use the
MenuHeaderComponent
prop. Best practice: Use or extendNestedMenuHeader
to ensure the back button and navigation remain functional.
Important:
- If you do not use a header (i.e., you do not use
NestedMenuHeader
or a compatible custom header), no back button will be shown in nested menus. - If you want no header at all, you must explicitly pass
null
to theMenuHeaderComponent
prop, since it defaults toNestedMenuHeader
.
Why use a header?
- Provides context for the current menu level.
- Enables back navigation for nested menus.
- Improves accessibility and usability.
NestedMenuHeader Properties
Extends the MenuHeader
component
Name | Type | Default | Description | Required? |
---|---|---|---|---|
title | string | |||
leftAction | React.ReactNode | |||
rightAction | React.ReactNode | |||
backArrowLabel | string | Localized label for the back arrow in this child menu.
Overrides the default label passed to NestedMenu. |
Menu Items
Menu items define the options available at each menu level.
- Use
NestedMenuItem
for items that open a child menu (submenu). - Use standard menu items for leaf nodes (no submenu).
How submenus work:
Add a childMenuId
prop to a NestedMenuItem
.
The value should match a key in the menus
object.
When a user selects a menu item with a childMenuId
, the corresponding submenu is shown.
NestedMenuItem Properties
Extends the MenuItem
component
Name | Type | Default | Description | Required? |
---|---|---|---|---|
children | React.ReactNode | |||
disabled | boolean | |||
role | (typeof MENU_ITEM_ROLES)[keyof typeof MENU_ITEM_ROLES] | |||
innerRef | React.Ref<any> | |||
$highlighted | boolean | |||
active | boolean | |||
inputType | (typeof INPUT_TYPES)[keyof typeof INPUT_TYPES] | |||
inputLabelId | string | |||
childMenuId | string | The id of the menu to be displayed when this item is selected. |
Defining Menus
Each entry in the menus
object defines a menu level.
You can use any of the select-style menu components (ActionMenu
, SingleSelectMenu
, MultiSelectMenu
) as a MenuComponent
.
Each menu can also have its own header and props.
Menu Object Structure
Key | Type | Description |
---|---|---|
MenuComponent | React.ComponentType | The menu component to render for this level (e.g., ActionMenu , SingleSelectMenu ). |
menuProps | Props for the chosen MenuComponent | Props to pass to the menu component. Should match the expected props for the chosen component. |
MenuHeaderComponent | React.ComponentType or null | (Optional) Custom header component for this menu. |
menuHeaderProps | Partial<TypeMenuHeaderProps> | (Optional) Props for the header component. |
Note:
To create a submenu, use thechildMenuId
prop on a menu item (such asNestedMenuItem
).
The value should match a key in themenus
object.
When a user selects a menu item with achildMenuId
, the corresponding submenu is shown.
Example:
const menus = { root: { MenuComponent: ActionMenu, menuProps: { menuItems: [ { id: "parent-1", children: "Open inner menu", childMenuId: "inner" }, { id: "other-root", children: "No child menu" }, ], }, MenuHeaderComponent: null, }, inner: { MenuComponent: ActionMenu, menuProps: { children: ( <NestedMenuContent> <NestedMenuItem id="item-2" children="Books" childMenuId="books" /> </NestedMenuContent> ), }, menuHeaderProps: { title: "Inner Menu" }, }, books: { MenuComponent: SingleSelectMenu, menuProps: { menuItems: [ { id: "book-1", children: "Book 1" }, { id: "book-2", children: "Book 2" }, ], }, menuHeaderProps: { title: "Books" }, },};
Recommended: Use Hooks for Each Menu
For menus with local state, async data, or complex logic, we recommend encapsulating each menu’s configuration in a custom hook. This keeps your menu definitions clean and makes it easier to manage selection, filtering, and dynamic content.
Why use hooks?
- Keeps menu logic isolated and reusable
- Makes it easier to manage local state (selection, filtering, etc)
- Encourages clean, maintainable code
Example Hooks
import React, { useState } from "react";import { ActionMenu, SingleSelectMenu, NestedMenuContent, NestedMenuItem,} from "@sproutsocial/seeds-react-menu";
export const useRootMenu = () => { const [selectedItem, setSelectedItem] = useState(); return { MenuComponent: SingleSelectMenu, menuProps: { menuItems: [ { id: "parent-1", children: "Open inner menu", childMenuId: "inner" }, { id: "other-root", children: "No child menu" }, ], selectedItem, onSelectedItemChange: ({ selectedItem }) => setSelectedItem(selectedItem), }, MenuHeaderComponent: null, };}
export const useInnerMenu = () => { // ... various api calls ... return { MenuComponent: ActionMenu, menuProps: { children: ( <NestedMenuContent> <NestedMenuItem id="item-2" children="Books" childMenuId="books" /> </NestedMenuContent> ), }, menuHeaderProps: { title: "Inner Menu" }, };}
Example NestedMenu
import React, { useState } from "react";import { NestedMenu, MenuToggleButton } from "@sproutsocial/seeds-react-menu";import { useRootMenu, useInnerMenu, useBooksMenu } from "..."; // import your hooks here
const ExampleNestedMenu = () => { const rootMenu = useRootMenu(); const innerMenu = useInnerMenu(); const booksMenu = useBooksMenu();
const menus = { root: rootMenu, inner: innerMenu, books: booksMenu, // ...other menus };
return ( <NestedMenu initialMenuId="root" menus={menus} menuToggleElement={<MenuToggleButton>Open Menu</MenuToggleButton>} /> );}
Note:
If your menu hooks depend on props or state, you may want to useuseMemo
when composing yourmenus
object.
This helps avoid unnecessary re-renders of your menu components when unrelated state changes.Example:
const menus = useMemo(() => ({root: rootMenu,inner: innerMenu,books: booksMenu,}), [rootMenu, innerMenu, booksMenu]);
Example using the hook without nesting
import React, { useState } from "react";import { MenuHeader, MenuToggleButton } from "@sproutsocial/seeds-react-menu";import { useInnerMenu } from "..."; // import your hooks here
const ExampleInnerMenu = () => { const { MenuComponent, menuProps: { children, ...restMenuProps }, menuHeaderProps, } = useInnerMenu();
return ( <MenuComponent menuToggleElement={<MenuToggleButton>Open Menu</MenuToggleButton>} {...restMenuProps} > <MenuHeader {...menuHeaderProps} /> {children} </MenuComponent> );}
NestedMenu Context
The NestedMenuContext
provides access to the current menu path, navigation helpers, and menu state.
You can use the useNestedMenuContext
hook inside any component rendered within a NestedMenu
to read or update the menu state.
Example:
import { useNestedMenuContext } from "@sproutsocial/seeds-react-menu";
const CustomBreadcrumbs = () => { const { selectedMenuPath, setSelectedMenuId, goBack } = useNestedMenuContext();
return ( <div> Path: {selectedMenuPath.join(" > ")} <button onClick={goBack}>Back</button> </div> );}
Context values include:
selectedMenuPath
: The array of menu IDs representing the current navigation path.setSelectedMenuId(id)
: Navigate directly to a specific menu.goBack()
: Navigate to the previous menu.rootMenuContextProps
,activeMenuContext
,setActiveMenuContext
,animationSpeed
: Advanced state and helpers for custom integrations.
Note:
useNestedMenuContext
must be used within aNestedMenu
or a component rendered inside it.