Sidebar
The Sidebar component is a responsive, collapsible navigation surface for an application. It expands and collapses on desktop, slides in as an off-canvas panel on mobile, and supports nested submenus, badges, tooltips, per-item actions, and a footer profile menu.
It’s a compound component with parts like Sidebar.Provider, Sidebar.Root, Sidebar.Trigger, Sidebar.Content, Sidebar.Header, Sidebar.Footer, Sidebar.Group, Sidebar.Item, Sidebar.MenuSubItem, Sidebar.Popover, Sidebar.Rail, Sidebar.Inset, and more.
Installation
import { Sidebar } from '@harnessio/ui/components'
// Basic usage with navigationconst SidebarExample = () => { return ( <Sidebar.Provider> <Sidebar.Root> <Sidebar.Header> {/* Logo or branding */} <div className="p-cn-md"> <YourLogo /> </div> </Sidebar.Header>
<Sidebar.Content> {/* Main navigation items */} <Sidebar.Group> <Sidebar.Item icon="dashboard" title="Dashboard" to="/dashboard" /> <Sidebar.Item title="Projects" badge="5" to="/projects" /> </Sidebar.Group>
{/* Grouped navigation with label */} <Sidebar.Group label="Settings"> <Sidebar.Item icon="settings" title="General Settings" to="/settings/general" /> <Sidebar.Item icon="users" title="Team Members" to="/settings/team" /> </Sidebar.Group> </Sidebar.Content>
<Sidebar.Footer> {/* User profile */} <Sidebar.Item src={user.avatar} avatarFallback={user.initials} title={user.name} description={user.email} dropdownMenuContent={ <> <DropdownMenu.Item>Profile</DropdownMenu.Item> <DropdownMenu.Item>Logout</DropdownMenu.Item> </> } /> <Sidebar.ToggleMenuButton /> </Sidebar.Footer>
<Sidebar.Rail /> </Sidebar.Root>
{/* Main content area */} <div className="flex-1"> <header className="border-b"> <Sidebar.Trigger /> </header> <Sidebar.Inset> {/* Your main application content */} </Sidebar.Inset> </div> </Sidebar.Provider> )}Anatomy
The Sidebar is composed of nested parts that together form a complete navigation surface. Sidebar.Provider holds the open/collapsed state and must wrap everything.
<Sidebar.Provider> <Sidebar.Root> <Sidebar.Header /> <Sidebar.Content> <Sidebar.Group> <Sidebar.Item> <Sidebar.MenuSubItem /> </Sidebar.Item> </Sidebar.Group> <Sidebar.Separator /> </Sidebar.Content> <Sidebar.Footer /> <Sidebar.Rail /> </Sidebar.Root> <Sidebar.Trigger /> <Sidebar.Inset /></Sidebar.Provider>Sidebar.Provider— owns the expanded/collapsed state (viaopen/onOpenChange, or uncontrolled withdefaultOpen) and shares it through context. State is also persisted to a cookie so it survives reloads.Sidebar.Root— the container that renders the sidebar on desktop and switches to a slide-inSheeton mobile.sideplaces it on the left or right.Sidebar.Content— the scrollable region for groups and items (role="menu");Sidebar.Header/Sidebar.Footerframe it.Sidebar.Group— a labeled cluster of items (role="group");Sidebar.Itemis the individual entry, with optional icon/logo/avatar, badge, actions, submenu, or dropdown.Sidebar.MenuSubItem— a nested link revealed when an item with children expands;Sidebar.Popovershows nested items in a flyout instead.Sidebar.Trigger/Sidebar.Rail/Sidebar.ToggleMenuButton— three ways to toggle collapse;Sidebar.Insetis the main content area that sits beside the sidebar.
Best practices
Do
- Group related destinations with
Sidebar.Group, and label groups once you have more than one so the structure is scannable. - Give every
Sidebar.Itema cleartitle— it’s the label shown when expanded and the tooltip shown when collapsed. - Provide a way to toggle the sidebar (
Sidebar.Trigger,Sidebar.Rail, orSidebar.ToggleMenuButton) so people can reclaim space. - Use
Sidebar.MenuSkeletonwhile a group’s items are loading, matching the number of rows you expect.
Don’t
- Don’t overload a single item —
badge,actionMenuItems,dropdownMenuContent,withRightIndicator, and a submenu (children) are mutually exclusive, so pick the one that fits. - Don’t nest more than one level of submenu; use
Sidebar.Popoverwhen a flyout reads better than an inline expand. - Don’t rely on the collapsed icon alone for unfamiliar destinations — keep titles meaningful so the tooltips are useful.
Accessibility
Keyboard
- Items, links, and the toggle controls are standard focusable controls, reachable and activatable from the keyboard. Submenu triggers respond to Enter / Space.
- Note: a
⌘B/Ctrl+Bshortcut is not currently wired up — toggle viaSidebar.Trigger,Sidebar.Rail, orSidebar.ToggleMenuButton.
Labeling
Sidebar.Contentexposesrole="menu",Sidebar.Groupexposesrole="group", and items render asrole="menuitem", so the structure is conveyed to assistive tech.- When the sidebar is collapsed, each item shows a tooltip with its
title.Sidebar.TriggerandSidebar.Railcarry their own accessible labels.
Focus & states
- Disabled items render with
aria-disabledand aren’t interactive. A closed submenu is hidden from assistive tech (aria-hidden) until it expands.
Properties
Sidebar.Provider
The provider that manages sidebar state and context. Must wrap all other sidebar parts.
Prop | Required | Default | Type |
|---|---|---|---|
| children | true | ReactNode | |
| defaultOpen | false | true | boolean |
| open | false | boolean | |
| onOpenChange | false | (open: boolean) => void | |
| className | false | string |
Also accepts standard <div> attributes.
Sidebar.Root
The main container. Renders the sidebar on desktop and a slide-in Sheet on mobile.
Prop | Required | Default | Type |
|---|---|---|---|
| children | true | ReactNode | |
| side | false | 'left' | 'left' | 'right' |
| className | false | string |
Also accepts standard <div> attributes (including style, for CSS variable overrides).
Sidebar.Trigger
A button that toggles the sidebar’s open/collapsed state.
Prop | Required | Default | Type |
|---|---|---|---|
| className | false | string | |
| onClick | false | (event: MouseEvent<HTMLButtonElement>) => void |
Accepts Button props (except iconOnly / tooltipProps).
Sidebar.Rail
An interactive rail on the edge of the sidebar that toggles it on click. Desktop only.
Prop | Required | Default | Type |
|---|---|---|---|
| open | false | boolean | |
| animate | false | false | boolean |
| className | false | string |
Sidebar.Inset
The main content region that sits beside the sidebar. Renders a <main> element.
Prop | Required | Default | Type |
|---|---|---|---|
| children | true | ReactNode | |
| className | false | string |
Sidebar.Header
Container for header content like logos or branding.
Prop | Required | Default | Type |
|---|---|---|---|
| children | true | ReactNode | |
| className | false | string |
Sidebar.Content
Scrollable container for the navigation groups and items (role="menu").
Prop | Required | Default | Type |
|---|---|---|---|
| children | true | ReactNode | |
| contentClassName | false | string | |
| className | false | string |
Sidebar.Footer
Container for footer content like a user profile or the collapse toggle.
Prop | Required | Default | Type |
|---|---|---|---|
| children | true | ReactNode | |
| className | false | string |
Sidebar.Group
Groups related items with an optional label and group-level action (role="group").
Prop | Required | Default | Type |
|---|---|---|---|
| children | true | ReactNode | |
| label | false | string | |
| actionIcon | false | 'list' | IconV2 name |
| onActionClick | false | () => void | |
| className | false | string |
Sidebar.Item
The main navigation item. badge, actionMenuItems, dropdownMenuContent, withRightIndicator, and a submenu (children) are mutually exclusive — choose the one variant that fits. Similarly, icon, logo, and the avatar (src / avatarFallback) are mutually exclusive.
Prop | Required | Default | Type |
|---|---|---|---|
| title | true | string | |
| description | false | string | |
| icon | false | IconV2 name | |
| logo | false | Logo name | |
| src | false | string | |
| avatarFallback | false | string | |
| badge | false | string | { content?: ReactNode; variant?: 'outline' | 'status'; theme?: StatusBadgeTheme; className?: string } | |
| actionMenuItems | false | DropdownMenuItemProps[] | |
| actionButtons | false | SidebarItemActionButton[] | |
| dropdownMenuContent | false | ReactNode | |
| withRightIndicator | false | boolean | |
| children | false | ReactNode | |
| to | false | string | |
| active | false | boolean | |
| onClick | false | (event: MouseEvent<HTMLButtonElement>) => void | |
| disabled | false | boolean | |
| className | false | string |
Sidebar.MenuSubItem
A nested navigation link shown inside an expanded Sidebar.Item.
Prop | Required | Default | Type |
|---|---|---|---|
| title | true | string | |
| to | true | string | |
| active | false | false | boolean |
| className | false | string |
Sidebar.Popover
Shows nested content in a flyout anchored to its trigger item.
Prop | Required | Default | Type |
|---|---|---|---|
| children | true | ReactNode | |
| content | true | ReactNode | |
| side | false | 'top' | 'right' | 'bottom' | 'left' | |
| sideOffset | false | number | |
| open | false | boolean | |
| onOpenChange | false | (open: boolean) => void |
Sidebar.Separator
A visual separator between groups or items.
Prop | Required | Default | Type |
|---|---|---|---|
| orientation | false | 'horizontal' | 'horizontal' | 'vertical' |
| className | false | string |
Sidebar.MenuSkeleton
A loading placeholder for a sidebar item.
Prop | Required | Default | Type |
|---|---|---|---|
| hideIcon | false | false | boolean |
| className | false | string |
Sidebar.ToggleMenuButton
A button for toggling the sidebar’s collapsed/expanded state, with a built-in tooltip.
Prop | Required | Default | Type |
|---|---|---|---|
| onClick | false | (event: MouseEvent<HTMLButtonElement>) => void |