Sidebar
A responsive sidebar navigation component with collapsible groups, rail mode, icon collapse, resizing, hover-to-peek, search, drag-to-reorder, and mobile sheet support.
Import
import { Sidebar, useSidebar } from '@darkcode-ui/react';Usage
"use client";
import {Sidebar} from "@darkcode-ui/react";
import {ChartColumn, Folder, Gear, House, ListUl, Persons, Sparkles} from "@gravity-ui/icons";
Anatomy
Import the Sidebar component and access all parts using dot notation.
import { Sidebar } from '@darkcode-ui/react';
export default () => (
<Sidebar.Provider>
<Sidebar>
<Sidebar.Header>
<Sidebar.Search />
</Sidebar.Header>
<Sidebar.Content>
<Sidebar.Group>
<Sidebar.GroupLabel />
<Sidebar.Menu>
<Sidebar.MenuSection>
<Sidebar.MenuHeader />
<Sidebar.MenuItem>
<Sidebar.MenuItemContent>
<Sidebar.MenuIcon />
<Sidebar.MenuLabel />
<Sidebar.MenuChip />
<Sidebar.MenuActions>
<Sidebar.MenuAction />
</Sidebar.MenuActions>
<Sidebar.MenuTrigger>
<Sidebar.MenuIndicator />
</Sidebar.MenuTrigger>
</Sidebar.MenuItemContent>
<Sidebar.Submenu>
<Sidebar.MenuItem />
</Sidebar.Submenu>
</Sidebar.MenuItem>
</Sidebar.MenuSection>
<Sidebar.MenuEmpty />
</Sidebar.Menu>
</Sidebar.Group>
<Sidebar.Separator />
</Sidebar.Content>
<Sidebar.Footer />
<Sidebar.Rail />
</Sidebar>
<Sidebar.Main>
<Sidebar.Trigger />
</Sidebar.Main>
</Sidebar.Provider>
);The menu is built on the React Aria Tree collection, so it ships full keyboard navigation, typeahead, and nested expand/collapse out of the box. <Sidebar> is responsive: on desktop it renders a fixed panel, and at mobile widths (≤768px) it automatically renders its children inside a slide-in Drawer.
Complex
A full-featured sidebar with teamspaces, favorites, nested menus, and a user footer.
"use client";
import {Sidebar} from "@darkcode-ui/react";
import {Bell, Code, House, Magnifier, Palette, Star} from "@gravity-ui/icons";
Compact With User Menu
Compact sidebar with a user button at the footer.
"use client";
import {Button, Sidebar} from "@darkcode-ui/react";
import {Folder, Gear, House, Persons} from "@gravity-ui/icons";
Meeting Notes
Notes-style sidebar with a search field and spaces. Uses collapsible="offcanvas".
"use client";
import {Button, Sidebar} from "@darkcode-ui/react";
import {Calendar, FileText, Magnifier, Person} from "@gravity-ui/icons";
Agent Hub
Agent-focused sidebar combining workspaces, recent chats, and a user footer.
"use client";
import {Sidebar} from "@darkcode-ui/react";
import {Comment, Sparkles, Wrench} from "@gravity-ui/icons";
Agent Workspace
Workspace sidebar with agent tasks grouped by status, inspired by AI coding tools.
"use client";
import {Sidebar} from "@darkcode-ui/react";
import {CircleCheck, CircleDashed, Code} from "@gravity-ui/icons";
Inset Variant
The inset variant uses a transparent sidebar background with a card-like content area.
"use client";
import {Sidebar} from "@darkcode-ui/react";
import {Folder, Gear, House, Persons} from "@gravity-ui/icons";
With Groups
Organize menu items into labeled groups separated by dividers.
"use client";
import {Sidebar} from "@darkcode-ui/react";
import {CreditCard, Folder, Gear, House, Person} from "@gravity-ui/icons";
Collapsible Groups
Use expandable groups for documentation-style navigation with guide lines.
"use client";
import {Sidebar} from "@darkcode-ui/react";
import {Brand, DemoMain, NavItem} from "./shared";Collapsible
Enable collapsible="icon" to allow the sidebar to collapse into an icon-only rail. Press Cmd+B / Ctrl+B to toggle.
"use client";
import {Sidebar} from "@darkcode-ui/react";
import {Folder, Gear, House, Persons} from "@gravity-ui/icons";
Reduced Motion
Pass reduceMotion to Sidebar.Provider to disable expand/collapse animations.
"use client";
import {Sidebar} from "@darkcode-ui/react";
import {Folder, House, Square} from "@gravity-ui/icons";
Floating Variant
The floating variant renders the sidebar with rounded corners and a shadow.
"use client";
import {Sidebar} from "@darkcode-ui/react";
import {Folder, Gear, House, Persons} from "@gravity-ui/icons";
With Avatar
Display an organization avatar in the sidebar header.
"use client";
import {Avatar, Sidebar} from "@darkcode-ui/react";
import {Folder, House, Persons} from "@gravity-ui/icons";
Right Side
Position the sidebar on the right by setting side="right" on the provider.
"use client";
import {Sidebar} from "@darkcode-ui/react";
import {Folder, Gear, House, Persons} from "@gravity-ui/icons";
Icon Only
A permanently collapsed icon-only sidebar with tooltips on each item.
"use client";
import type {ReactNode} from "react";
import {Avatar, Sidebar} from "@darkcode-ui/react";Resizable Width
Set resizable on Sidebar.Provider to drag the rail and resize the sidebar. The width is clamped between minWidth and maxWidth, and persisted to a sidebar_width cookie.
"use client";
import {Sidebar} from "@darkcode-ui/react";
import {ChartColumn, Folder, House, ListUl, Persons} from "@gravity-ui/icons";
Expand On Hover
With collapsible="icon", set expandOnHover to float the collapsed panel open on hover without shifting the page.
"use client";
import {Avatar, Sidebar} from "@darkcode-ui/react";
import {ChartColumn, Folder, House, ListUl, Persons} from "@gravity-ui/icons";
Searchable
Add Sidebar.Search to filter menu items by their text. Render Sidebar.MenuEmpty to show a message when nothing matches.
"use client";
import {Sidebar} from "@darkcode-ui/react";
import {Bell, ChartColumn, Folder, Gear, House, ListUl, Persons} from "@gravity-ui/icons";
Reorderable
Set allowReorder and provide an onReorder handler on Sidebar.Menu to reorder items by drag-and-drop. The menu's order is controlled by your data.
"use client";
import type {ReactNode} from "react";
import {Sidebar} from "@darkcode-ui/react";Keyboard Shortcut
By default the sidebar toggles on Cmd+B (macOS) or Ctrl+B (other platforms). Override it via the toggleShortcut prop on Sidebar.Provider:
<Sidebar.Provider toggleShortcut="mod+shift+s">{/* … */}</Sidebar.Provider>The combo string joins modifiers with +. Recognized modifiers are mod (Cmd on macOS, Ctrl elsewhere), cmd, ctrl, meta, shift, and alt. Finish with a single key (e.g. "mod+\\", "ctrl+k", "alt+shift+s").
To disable the shortcut entirely, pass false or null:
<Sidebar.Provider toggleShortcut={false}>{/* … */}</Sidebar.Provider>The handler calls preventDefault() when the shortcut matches. If your app owns its own global keyboard map, disable this shortcut and wire the toggle yourself via useSidebar.
Persisted State
When Sidebar.Provider is uncontrolled (no open prop), it automatically writes the sidebar state to a sidebar_state cookie on every toggle. To restore the state across page loads, read that cookie server-side and pass it as defaultOpen.
// app/layout.tsx (Next.js App Router)
import { cookies } from "next/headers";
import { Sidebar } from "@darkcode-ui/react";
export default async function Layout({ children }: { children: React.ReactNode }) {
const store = await cookies();
const defaultOpen = store.get("sidebar_state")?.value !== "false";
return (
<Sidebar.Provider defaultOpen={defaultOpen}>
<Sidebar>{/* … */}</Sidebar>
<Sidebar.Main>{children}</Sidebar.Main>
</Sidebar.Provider>
);
}Client-Side Routing
Sidebar.MenuItem supports navigation via the href prop. Because React Aria TreeItem elements cannot be rendered as <a> tags, navigation is handled programmatically through a navigate callback on Sidebar.Provider. Pass your router's navigation function and add href to any item that should navigate:
"use client";
import { useRouter } from "next/navigation";
function AppSidebar() {
const router = useRouter();
return (
<Sidebar.Provider navigate={router.push}>
<Sidebar>
<Sidebar.Content>
<Sidebar.Menu aria-label="Main">
<Sidebar.MenuItem href="/dashboard">
<Sidebar.MenuItemContent>
<Sidebar.MenuLabel>Dashboard</Sidebar.MenuLabel>
</Sidebar.MenuItemContent>
</Sidebar.MenuItem>
</Sidebar.Menu>
</Sidebar.Content>
</Sidebar>
</Sidebar.Provider>
);
}External URLs (http://, https://) automatically open in a new tab. For internal links that need a full page reload, set forceReload. If no navigate function is provided, href falls back to window.location.href.
Resizing
Set resizable to let users drag the rail to resize the panel. A click on the rail still toggles the sidebar; a drag resizes it. The width is clamped to minWidth/maxWidth (default 200–420).
When uncontrolled, the chosen width is written to a sidebar_width cookie. Restore it on the next load by reading the cookie and passing defaultWidth — the same pattern as persisted open state:
import { cookies } from "next/headers";
const store = await cookies();
const defaultWidth = Number(store.get("sidebar_width")?.value) || undefined;
<Sidebar.Provider resizable defaultWidth={defaultWidth}>{/* … */}</Sidebar.Provider>For full control, pass width and onWidthChange.
Active Indicator
The current item (isCurrent) shows an accent bar. By default (animateActiveIndicator), component-initiated navigation (items with href) is wrapped in document.startViewTransition so the bar morphs to its new position where supported. For fully-controlled isCurrent, wrap your own state change in startViewTransition to get the same effect. The transition is skipped automatically when reduceMotion is set or the user prefers reduced motion.
Search & Filtering
Sidebar.Search drives a search query through context; Sidebar.MenuItem hides itself when its text doesn't match. Both live anywhere inside Sidebar.Provider. Use Sidebar.MenuEmpty to render a fallback when no items match.
<Sidebar.Header>
<Sidebar.Search placeholder="Search…" />
</Sidebar.Header>
<Sidebar.Content>
<Sidebar.Menu aria-label="Main">
{/* …items… */}
<Sidebar.MenuEmpty>No results found</Sidebar.MenuEmpty>
</Sidebar.Menu>
</Sidebar.Content>Filtering matches items that are currently rendered. Items inside a collapsed submenu are not rendered by the underlying Tree, so they aren't matched until expanded. Use a flat menu for fully searchable navigation.
Reordering
Set allowReorder and an onReorder handler on Sidebar.Menu to enable drag-and-drop reordering (built on React Aria's useDragAndDrop). Items need stable ids, and the order is controlled — apply the reorder to your own data in onReorder:
<Sidebar.Menu
aria-label="Main"
allowReorder
items={items}
onReorder={(e) => setItems((prev) => reorder(prev, e))}
>
{(item) => (
<Sidebar.MenuItem id={item.id} textValue={item.label}>
{/* … */}
</Sidebar.MenuItem>
)}
</Sidebar.Menu>Keyboard drag-and-drop is supported out of the box via the Tree collection.
API Reference
Sidebar.Provider
Layout wrapper that provides sidebar context to all children. Wraps both the sidebar and main content.
| Prop | Type | Default | Description |
|---|---|---|---|
open | boolean | — | Controlled open/expanded state. |
defaultOpen | boolean | true | Initial open state (uncontrolled). |
onOpenChange | (open: boolean) => void | — | Callback when open state changes. |
side | "left" | "right" | "left" | Which side the sidebar is on. |
variant | "sidebar" | "floating" | "inset" | "sidebar" | Visual style variant. |
collapsible | "icon" | "offcanvas" | "none" | "icon" | Collapse behavior. |
navigate | (href: string) => void | — | Programmatic navigation function for client-side routing. |
reduceMotion | boolean | false | Disable expand/collapse animations. |
animateActiveIndicator | boolean | true | Morph the active-item indicator between items via View Transitions. |
expandOnHover | boolean | false | When collapsible="icon", expand to a floating panel on hover. |
resizable | boolean | false | Allow resizing the sidebar by dragging the rail. |
width | number | — | Controlled width in px. |
defaultWidth | number | — | Initial width in px (uncontrolled). Falls back to the CSS default. |
onWidthChange | (width: number) => void | — | Called when the width changes via dragging. |
minWidth | number | 200 | Minimum width in px when resizable. |
maxWidth | number | 420 | Maximum width in px when resizable. |
toggleShortcut | string | false | null | "mod+b" | Keyboard shortcut that toggles the sidebar. |
backdrop | "blur" | "opaque" | "transparent" | "blur" | Backdrop style for the mobile sheet. |
Sidebar
The responsive sidebar panel. Renders an <aside> on desktop and a Drawer on mobile.
Sidebar.Header / Sidebar.Footer
Top and bottom sections. Render a <div> with a flex column layout.
Sidebar.Content
Scrollable middle section. Wraps ScrollShadow with hideScrollBar. Also supports all ScrollShadow props.
Sidebar.Group
Groups related content with optional spacing between groups.
| Prop | Type | Default | Description |
|---|---|---|---|
closeMobileOnAction | boolean | true | Whether pressing a menu item in this group closes the mobile sheet. Inherited by descendant menus and items. |
Sidebar.GroupLabel
Label for a group. Hidden when the sidebar is collapsed.
Sidebar.Search
Search input that filters menu items by text. Built on React Aria SearchField (clears on Escape and via the clear button). Place it anywhere inside Sidebar.Provider.
| Prop | Type | Default | Description |
|---|---|---|---|
placeholder | string | "Search" | Placeholder text for the input. |
aria-label | string | "Search" | Accessible label for the search field. |
Sidebar.MenuEmpty
Renders its children (default: "No results found") only while a search is active and no items match.
Sidebar.Menu
Tree-based menu container backed by React Aria Tree. Supports keyboard navigation and guide lines.
| Prop | Type | Default | Description |
|---|---|---|---|
closeMobileOnAction | boolean | inherited | Whether pressing a menu item closes the mobile sheet. |
reduceMotion | boolean | inherited | Disable submenu animations for this menu only. |
showGuideLines | boolean | "hover" | true | Submenu guide lines. true = always, false = never, "hover" = on hover. |
allowReorder | boolean | false | Enable drag-and-drop reordering. Requires keyed items and onReorder. |
onReorder | (e) => void | — | Called when items are dropped into a new order (RAC reorder event). |
Also supports all React Aria Tree props (selection-related props are disallowed).
Sidebar.MenuItem
An individual menu item that can contain icons, labels, actions, chips, and nested submenus.
| Prop | Type | Default | Description |
|---|---|---|---|
textValue | string | derived | Typeahead/accessibility label. Derived from text content when omitted. |
href | string | — | URL to navigate to. Uses the Provider's navigate, or window.location.href as a fallback. |
forceReload | boolean | false | Force a full page reload instead of client-side navigation. |
isCurrent | boolean | false | Marks the item as the current page (aria-current="page"). |
tooltip | ReactNode | — | Tooltip content shown when collapsed to icon-only. |
closeMobileOnAction | boolean | inherited | Override the inherited value for this item. |
Also supports all React Aria TreeItem props.
Sidebar.MenuItemContent
Wrapper for the visible row content inside a menu item.
Sidebar.MenuIcon / Sidebar.MenuLabel / Sidebar.MenuChip
Icon slot, label text, and trailing badge/counter inside a menu item.
Sidebar.MenuActions / Sidebar.MenuAction
Container and individual action buttons. Hidden by default, shown on hover or when the item is current.
Sidebar.MenuTrigger
Expand/collapse toggle for items with children. Hidden when the item has no child items.
Sidebar.MenuIndicator
Chevron icon that rotates when an item is expanded. Accepts custom children.
Sidebar.Submenu
Wrapper for nested child items. Children are rendered as tree sub-items.
Sidebar.Separator
Horizontal divider. Wraps Separator.
Sidebar.Rail
Thin edge strip for toggling the sidebar by clicking.
Sidebar.Trigger
Toggle button. Renders a Button with variant="ghost" and size="sm".
Sidebar.Main
Main content area next to the sidebar. Renders a <main> with flex-1.
Sidebar.Mobile
API-compat passthrough — <Sidebar> already renders a Drawer on mobile. Configure the mobile backdrop via the backdrop prop on Sidebar.Provider.
Sidebar.Tooltip
Tooltip wrapper that only shows when the sidebar is collapsed. When expanded, renders children directly.
| Prop | Type | Default | Description |
|---|---|---|---|
content | ReactNode | — | Tooltip content shown when collapsed. |
placement | "top" | "bottom" | "left" | "right" | "right" | Tooltip placement. |
delay | number | — | Delay in ms before showing. |
closeDelay | number | — | Delay in ms before hiding. |
useSidebar
A hook for programmatic sidebar control. Must be used within a Sidebar.Provider.
const {
isOpen, setOpen,
isMobile, isMobileOpen, setMobileOpen,
toggleSidebar,
side, variant, collapsible,
} = useSidebar();| Property | Type | Description |
|---|---|---|
isOpen | boolean | Current desktop open/expanded state. |
setOpen | (open: boolean) => void | Set the desktop open state. |
isMobile | boolean | Whether the viewport is at mobile width (≤768px). |
isMobileOpen | boolean | Current mobile sheet open state. |
setMobileOpen | (open: boolean) => void | Set the mobile sheet open state. |
toggleSidebar | () => void | Toggle the sidebar (desktop or mobile depending on viewport). |
side | "left" | "right" | Which side the sidebar is on. |
variant | "sidebar" | "floating" | "inset" | Current visual variant. |
collapsible | "icon" | "offcanvas" | "none" | Current collapse behavior. |
isResizable | boolean | Whether the sidebar can be resized by dragging the rail. |
setWidth | (width: number) => void | Set the sidebar width in px (clamped to minWidth/maxWidth). |
minWidth / maxWidth | number | Resize bounds in px. |
expandOnHover | boolean | Whether the collapsed icon panel expands on hover. |
CSS Variables
| Variable | Default | Description |
|---|---|---|
--sidebar-width | 240px | Expanded sidebar width. |
--sidebar-width-collapsed | 48px | Collapsed width for icon mode. |
--sidebar-duration | 200ms | Transition duration. |
--sidebar-ease | var(--ease-out-fluid) | Transition easing. |
--sidebar-ease-collapse | var(--sidebar-ease) | Easing for the collapse/expand width change. |
--sidebar-edge-outline | var(--border) | Border color for the default variant edge. |
--sidebar-menu-indent | calc(var(--spacing) * 4) | Indentation per nesting level. |
--sidebar-menu-guide-color | color-mix(in srgb, currentColor 10%, transparent) | Guide line color. |
--sidebar-menu-row-gap | calc(var(--spacing) * 0.5) | Gap between menu rows. |