NavBar
Primary app navigation. The same component renders either horizontally
(top bar) or vertically (side rail), driven by one items tree. Items
can carry nested children to produce dropdowns on horizontal and
expand-collapse groups on vertical. Below the md breakpoint the bar
collapses to a hamburger that opens a Drawer containing the items —
opt out with responsive={false}.
Horizontal#
The default layout. Brand on the left, items in the middle, actions on
the right. Items without children render as plain links; items with
children open a dropdown via @radix-ui/react-navigation-menu.
Vertical#
Same data, side rail. Groups with children expand inline. The group
auto-opens when one of its descendants is the active item, so deep links
land on a discoverable item without a manual click.
With submenus (horizontal)#
Top-level items can declare children to open a dropdown. Disabled
children are still rendered but skip activation.
With expandable groups (vertical)#
Same children shape — the vertical layout shows them as collapsible
groups instead of dropdowns. Active state propagates to the parent
trigger so the visual hierarchy matches what the user is looking at.
Active state#
Active state is either controlled via value + onValueChange, or
uncontrolled via defaultValue. An item is "active" when its id
matches value; for groups, the trigger is treated as active when any
descendant is active.
const [active, setActive] = useState('overview');
<NavBar items={items} value={active} onValueChange={setActive} />;Items with hrefs#
When an item has an href, it renders as an <a> and onValueChange
fires alongside the native link navigation — convenient for keeping a
"current page" highlight in sync with router-driven navigation. Items
without href render as <button> elements, so keyboard activation
(Enter / Space) works without extra wiring.
Mobile / narrow viewports#
By default, the bar collapses to a top-row hamburger below md
(768px) and the items move into a left-side Drawer. Both orientations
share the same drawer body so users on small screens get the vertical
list regardless of which orientation the desktop layout uses. Pass
responsive={false} if your app handles its own responsive behavior.
Props#
| Prop | Type | Default | Description |
|---|---|---|---|
| orientation | enum | horizontal | Layout direction. Default `'horizontal'`. |
| items * | NavBarItem[] | — | Item tree driving the bar. |
| brand | ReactNode | — | Brand / logo slot rendered at the start. When `responsive` is `true`, `brand` also seeds the mobile Drawer's accessible name, so it should include text — e.g. `<><Logo /> ShipIt</>` rather than `<Logo />` alone. Falls back to `'Navigation'` when omitted. |
| actions | ReactNode | — | Trailing slot for secondary actions (avatar, settings, theme toggle, …). |
| value | string | undefined | — | Controlled active item id. |
| defaultValue | string | undefined | — | Uncontrolled initial active item id. |
| onValueChange | ((id: string) => void) | undefined | — | Fired when an item is activated. |
| width | number | undefined | 240 | Pixel width of the vertical rail. Default 240. |
| responsive | boolean | undefined | true | Collapse to a hamburger drawer below `md`. Default `true`. |