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.

NestedMenu is part of the Menu ecosystem. For use cases that are common across all menu types, such as keyboard navigation, accessibility, and menu positioning, see the Menu package documentation.
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>
<NestedMenuItem
id="item-2"
children="Books as ActionMenu"
childMenuId="books-action"
/>
<NestedMenuItem
id="item-3"
children="Books as MultiSelect"
childMenuId="books-multiselect"
/>
<NestedMenuItem
id="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%">
<NestedMenu
initialMenuId="root"
menus={menus}
backArrowLabel="Go Back"
menuToggleElement={<MenuToggleButton>Open Menu</MenuToggleButton>}
/>
</Box>
)
}

Properties

NameTypeDefaultDescriptionRequired?
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.

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 extend NestedMenuHeader 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 the MenuHeaderComponent prop, since it defaults to NestedMenuHeader.

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

NameTypeDefaultDescriptionRequired?
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 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

NameTypeDefaultDescriptionRequired?
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.

KeyTypeDescription
MenuComponentReact.ComponentTypeThe menu component to render for this level (e.g., ActionMenu, SingleSelectMenu).
menuPropsProps for the chosen MenuComponentProps to pass to the menu component. Should match the expected props for the chosen component.
MenuHeaderComponentReact.ComponentType or null(Optional) Custom header component for this menu.
menuHeaderPropsPartial<TypeMenuHeaderProps>(Optional) Props for the header component.

Note:
To create a submenu, use the childMenuId prop on a menu item (such as 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.

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" },
},
};

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 use useMemo when composing your menus 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 a NestedMenu or a component rendered inside it.


See Also