Skip to content

Menu

Menu is a vertical navigation menu, such as an app or docs sidebar: rows with icons, submenus that expand in place, titled groups and dividers, with one selected row. It renders a <nav> landmark; give it an aria-label. For a menu that pops up from a button, use Dropdown.

Demos

Basic

Options are a tree: rows { key, label, icon? }, submenus (rows with children), groups (type: "group") and dividers (type: "divider"). The selected row’s submenus start open, and a submenu holding it is marked too.

A row with href is a link with aria-current="page" while selected. renderLink renders those rows with your router’s link component: spread the props it receives and keep its onClick, which selects the row.

Collapsed

collapsed shrinks the menu to collapsedWidth and shows only icons (a label’s first letter when a row has none). A tooltip names each row, and a submenu opens its rows in a popup beside the menu.

Accordion

With accordion, opening a submenu closes the open submenus next to it.

Controlled expanded keys

expandedKeys and onUpdateExpandedKeys control which submenus are open.

Indent

rootIndent is the top level’s start padding and indent is added per level. By default the indent is the icon’s width plus its gap, so a child’s label lines up with its parent’s.

Extra and disabled

extra puts trailing content, such as a count, at the end of a row. disabled rows can’t be focused or chosen.

Accessibility

The menu follows the disclosure navigation pattern, not role="menu": menu roles are for application menus of commands and switch screen readers into a mode where links and Tab stop working as usual. Here every row is a plain link or button in the Tab order, a submenu is a button with aria-expanded and aria-controls, a group is a list labelled by its title, and the selected row has aria-current (page on links, true on buttons).

Keyboard

Tab moves through the rows. Enter (and Space on buttons) chooses a row or opens a submenu. As a shortcut, ↑ / ↓ move between visible rows, Home / End jump to the ends, → opens a submenu or enters an open one, and ← closes it or goes back to the submenu a row is in.

Props

NameTypeDefaultDescriptionVersion
optionsMenuMixedOption[]requiredRows, submenus, groups and dividers; see below.Unreleased
value / defaultValueMenuKey | nullundefined / nullControlled / initial selected key.Unreleased
onUpdateValue(key, option) => voidundefinedCalled when a row without children is chosen.Unreleased
expandedKeys / defaultExpandedKeysMenuKey[]undefinedControlled / initial open submenus. By default the selected row’s ancestors start open.Unreleased
defaultExpandAllbooleanfalseOpen every submenu at first.Unreleased
onUpdateExpandedKeys(keys: MenuKey[]) => voidundefinedCalled when a submenu opens or closes.Unreleased
accordionbooleanfalseOpening a submenu closes its open siblings.Unreleased
indent / rootIndentnumberthemeStart padding per level / of the top level, in px.Unreleased
collapsedbooleanfalseIcons only; labels in tooltips, submenus in popups.Unreleased
collapsedWidthnumbertheme (48 / 52)Width in px while collapsed.Unreleased
iconSize / collapsedIconSizenumberthemeIcon size in px, expanded / collapsed.Unreleased
disabledbooleanfalseDisable every row.Unreleased
renderLabel(option) => ReactNodeundefinedReplaces the label of rows and group titles.Unreleased
renderLink(props: MenuLinkProps, option) => ReactNodeundefinedRenders rows with an href. Defaults to an <a>.Unreleased

Any other prop, and ref, goes to the <nav>.

NameTypeDescription
keystring | numberRequired and unique.
labelReactNodeThe row’s text.
iconReactNodeBefore the label; alone when collapsed.
extraReactNodeAt the end of the row.
hrefstringMakes the row a link. Ignored on submenus.
disabledbooleanCan’t be chosen or opened.
showbooleanfalse leaves the option out.
childrenMenuMixedOption[]Makes the row a submenu.

Groups are { type: "group", key, label, children } and dividers { type: "divider", key }.

Notes

  • Only mode="vertical" for now; a horizontal top bar is planned separately.
  • Labels, icons and extras are ReactNodes. For links, use href + renderLink rather than a router link inside the label, so the whole row is the link.
  • A group’s rows stay at the group title’s depth, as in most docs sidebars.
  • accordion applies at every level.