27.7k

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.

PropTypeDefaultDescription
openboolean—Controlled open/expanded state.
defaultOpenbooleantrueInitial 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.
reduceMotionbooleanfalseDisable expand/collapse animations.
animateActiveIndicatorbooleantrueMorph the active-item indicator between items via View Transitions.
expandOnHoverbooleanfalseWhen collapsible="icon", expand to a floating panel on hover.
resizablebooleanfalseAllow resizing the sidebar by dragging the rail.
widthnumber—Controlled width in px.
defaultWidthnumber—Initial width in px (uncontrolled). Falls back to the CSS default.
onWidthChange(width: number) => void—Called when the width changes via dragging.
minWidthnumber200Minimum width in px when resizable.
maxWidthnumber420Maximum width in px when resizable.
toggleShortcutstring | false | null"mod+b"Keyboard shortcut that toggles the sidebar.
backdrop"blur" | "opaque" | "transparent""blur"Backdrop style for the mobile sheet.

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.

PropTypeDefaultDescription
closeMobileOnActionbooleantrueWhether 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.

PropTypeDefaultDescription
placeholderstring"Search"Placeholder text for the input.
aria-labelstring"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.

PropTypeDefaultDescription
closeMobileOnActionbooleaninheritedWhether pressing a menu item closes the mobile sheet.
reduceMotionbooleaninheritedDisable submenu animations for this menu only.
showGuideLinesboolean | "hover"trueSubmenu guide lines. true = always, false = never, "hover" = on hover.
allowReorderbooleanfalseEnable 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.

PropTypeDefaultDescription
textValuestringderivedTypeahead/accessibility label. Derived from text content when omitted.
hrefstring—URL to navigate to. Uses the Provider's navigate, or window.location.href as a fallback.
forceReloadbooleanfalseForce a full page reload instead of client-side navigation.
isCurrentbooleanfalseMarks the item as the current page (aria-current="page").
tooltipReactNode—Tooltip content shown when collapsed to icon-only.
closeMobileOnActionbooleaninheritedOverride 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.

PropTypeDefaultDescription
contentReactNode—Tooltip content shown when collapsed.
placement"top" | "bottom" | "left" | "right""right"Tooltip placement.
delaynumber—Delay in ms before showing.
closeDelaynumber—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();
PropertyTypeDescription
isOpenbooleanCurrent desktop open/expanded state.
setOpen(open: boolean) => voidSet the desktop open state.
isMobilebooleanWhether the viewport is at mobile width (≤768px).
isMobileOpenbooleanCurrent mobile sheet open state.
setMobileOpen(open: boolean) => voidSet the mobile sheet open state.
toggleSidebar() => voidToggle 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.
isResizablebooleanWhether the sidebar can be resized by dragging the rail.
setWidth(width: number) => voidSet the sidebar width in px (clamped to minWidth/maxWidth).
minWidth / maxWidthnumberResize bounds in px.
expandOnHoverbooleanWhether the collapsed icon panel expands on hover.

CSS Variables

VariableDefaultDescription
--sidebar-width240pxExpanded sidebar width.
--sidebar-width-collapsed48pxCollapsed width for icon mode.
--sidebar-duration200msTransition duration.
--sidebar-easevar(--ease-out-fluid)Transition easing.
--sidebar-ease-collapsevar(--sidebar-ease)Easing for the collapse/expand width change.
--sidebar-edge-outlinevar(--border)Border color for the default variant edge.
--sidebar-menu-indentcalc(var(--spacing) * 4)Indentation per nesting level.
--sidebar-menu-guide-colorcolor-mix(in srgb, currentColor 10%, transparent)Guide line color.
--sidebar-menu-row-gapcalc(var(--spacing) * 0.5)Gap between menu rows.

On this page