NavMenu
nav-menuRenders controlled multilevel navigation in tree or semantic list-and-link mode with inline and collapsed presentation.
Usage
Basic usage
In inline mode, the accordion in the submenu is expanded, and for uncontrolled use, defaultSelectedKeys is initially selected.
<NavMenu
items={items}
mode="inline"
defaultSelectedKeys={["dashboard"]}
/>Site main navigation semantics (semantics="list")
The default tree file is added to the line with role=treeitem, which will override the implicit link role of <a> - even the main navigation in the screen reading "List all links on the page" cannot be listed. The list file does not write role: <a> is link, <button> is button, and the keyboard returns to Tab item-by-item + native activation. The skin is exactly the same, only the accessibility tree has been changed. The file tree/outline tree is left at the default tree file.
<NavMenu
items={items}
semantics="list"
defaultSelectedKeys={["dashboard"]}
/>Expand submenu by default
defaultOpenKeys Specifies the parent item for initial expansion, and selects and locates the current page in conjunction with the child item.
<NavMenu
items={items}
mode="inline"
defaultOpenKeys={["users"]}
defaultSelectedKeys={["users-roles"]}
/>End of line operation
The actions slot is rendered outside the row button (absolutely covering the right side), and can be displayed using group-hover/nav-row instead of hover.
const items = [
{
type: "group",
key: "today",
label: "Today",
children: [
{ key: "c1", label: "How to access the Hulian component library", actions: <DeleteAction /> },
{ key: "c2", label: "Help me polish a weekly report", actions: <DeleteAction /> },
],
},
];
<NavMenu items={items} defaultSelectedKeys={["c1"]} />Collapsed state (icon track)
collapsed mode collapses into an icon track, hover / submenu pops up when focused.
<NavMenu
items={items}
mode="collapsed"
defaultSelectedKeys={["dashboard"]}
/>Collapse · Multi-level cascade
The fly-out layer of collapsed supports infinite levels like inline: sub-layers are cascaded to the right step by step. Keyboard → enters the sub-layer, ← / Esc returns to the parent layer, ↑↓ moves between brothers on the same layer.
const items = [
{
key: "sys",
label: "System Management",
icon: <Settings />,
children: [
{
key: "sys-user",
label: "Users and Permissions",
children: [
{
key: "sys-user-role",
label: "Character",
children: [
{ key: "sys-user-role-list", label: "Character List", href: "#role-list" },
{ key: "sys-user-role-perm", label: "Permission allocation", href: "#role-perm" },
],
},
],
},
],
},
];
<NavMenu items={items} mode="collapsed" defaultSelectedKeys={["sys-user-role-perm"]} />When to use
Use NavMenu for a multilevel sidebar in an admin application. It supports expanded inline navigation, collapsed icon flyouts, controlled or uncontrolled selection and expansion, and trailing row actions such as deleting a conversation. Use Navbar for a horizontal top bar, NavigationMenu for navigation with dropdown panels, or Menu for contextual actions.
Import
import { NavMenu } from "@hulianui/ui"Props
Each items entry is a NavMenuNode = NavMenuItem | NavMenuGroup. NavMenuItem is { key; label; icon?; href?; render?; disabled?; actions?; children? }: an item with children expands, an item with href renders a native <a>, and render supplies a framework link or another host element. NavMenuGroup is { type:"group"; key; label; children }; it renders a non-collapsible heading whose key does not participate in selection or expansion. NavMenu inherits native <nav> attributes except onSelect.
| Name | Type | Default | Description |
|---|---|---|---|
| items* | NavMenuNode[] | — | Hierarchical menu data. |
| mode | "inline" | "collapsed" | "inline" | Inline accordion or collapsed sidebar icons with flyout submenus. Both modes support unlimited depth. |
| semantics | "tree" | "list" | "tree" | Accessibility model. In list mode, rows do not receive an overriding role, preserving native link semantics, and keyboard interaction returns to normal Tab order. Use list for site navigation and retain tree for true file or outline trees. |
| selectedKeys | string[] | — | Controlled selected keys. |
| defaultSelectedKeys | string[] | — | Initial selected keys when uncontrolled. |
| openKeys | string[] | — | Controlled expanded keys in inline mode. |
| defaultOpenKeys | string[] | — | Initial expanded keys when uncontrolled. |
Events
| Event | Type | Description |
|---|---|---|
| onSelect | (key: string, item: NavMenuItem) => void | Called when a leaf item is selected. |
| onOpenChange | (openKeys: string[]) => void | Called when inline expansion changes. |
Usage guidelines
The default navigation accessibility label follows ConfigProvider ("Sidebar navigation" in enUS, Chinese in zhCN). An explicit aria-label still takes precedence.
- Put trailing controls in
actions; the component positions them outside the tree-item button or link. Do not put interactive elements such as `<button>` directly inside `label`. That creates invalid nested controls and can cause hydration errors. Actions are available only in inline mode. - Expansion uses a CSS
grid-template-rowstransition from0frto1fr, avoiding JavaScript height measurement and preserving smooth nested expansion. See [[nested-collapsible-height-via-css-grid-rows-not-js-measure]]. - Use either controlled state (
selectedKeysoropenKeysplus callbacks) or uncontrolled initial state (default*) for each dimension, not both. - Collapsed flyouts support unlimited cascading depth and keep the whole tree mounted. CSS
:hoverand:focus-withinexpose each level. In tree mode, keyboard navigation follows cascading-menu behavior:→enters a child level;←orEscreturns;↑and↓move only among siblings; andHomeorEndmoves to the first or last item at the current level. Roving tabindex provides one tab stop for the whole tree, so do not expect Tab to visit every flyout entry. Insemantics="list"mode, this keyboard contract yields entirely to native Tab order. - `semantics` changes the accessibility tree, not the appearance. Both modes keep the same styling, indentation, selection state, and flyout behavior, so the wrong choice is invisible and produces no runtime error. Explicitly use
semantics="list"for site navigation or screen-reader link lists will contain none of its destinations. - The first collapsed flyout uses
position: fixedwith measured coordinates. This prevents a scrollable sidebar ancestor, including AdminLayout's ScrollArea, from clipping the panel. Deeper levels remainabsolute. Coordinates update on capturedscrollandresizeevents; a custom scroller that emits no scroll event can leave the panel misaligned. openKeysapplies only to inline mode. Collapsed visibility follows hover and focus, does not enteropenKeys, and does not callonOpenChange.- Native menubar, SwiftUI, and Tauri menu caveats do not apply; this is a React WAI-ARIA tree.
Related
Navbar · BeianFooter · NavigationMenu · Menu · Menubar · Dock
Playground
<NavMenu
mode="inline"
items={items}
defaultOpenKeys={["users"]}
selectedKeys={selected}
onSelect={(key) => setSelected([key])}
/>