Sidebar

Navigates through a collapsible desktop rail that becomes a modal panel on smaller screens.

import { Sidebar } from "@usefragments/ui";Source

Preview

import { House as HouseIcon, ListBullets as ListIcon } from "@phosphor-icons/react";
import { Sidebar } from '@usefragments/ui';

<Sidebar>
  <Sidebar.Header>Workspace</Sidebar.Header>
  <Sidebar.Nav>

Usage

Use when

  • Primary navigation for an app shell
  • Navigation that must collapse to icons or hide entirely
  • Grouped destinations with active state

Avoid when

  • Top-level horizontal navigation (use Header)
  • Temporary side panels for one task (use Drawer)
  • In-page section links (use TableOfContents)

Implementation notes

  • Give every item an icon so the collapsed rail stays readable; with no glyphs, collapsing hides the rail
  • collapsible="icon" shrinks to the glyph rail; "offcanvas" takes it off the canvas, and Header.Trigger brings it back
  • Sidebar.Header shows collapsedContent alone while the rail is collapsed; give it the brand mark so a full brand never clips
  • Set state on Sidebar when it stands alone; wrap in Sidebar.Provider (or AppShell) when a header trigger or other code must drive it, and set state there
  • The current item takes the neutral current fill and ink 1 at the regular weight, with no ring; mark it with active
  • Sidebar.Item and Sidebar.SubItem take render for router links
  • Sidebar rows also work outside a Sidebar, for example in a Drawer for a header's mobile navigation
  • Use Sidebar.MenuSkeleton inside a Section while navigation data loads

Installation

pnpm add @usefragments/ui

Import styles once from @usefragments/ui/styles. See the installation guide for framework and theme setup.

Anatomy

Import Sidebar once, then compose its parts with dot notation.

  • Sidebar.Provider
  • Sidebar.Header
  • Sidebar.Nav
  • Sidebar.Section
  • Sidebar.SectionAction
  • Sidebar.Item
  • Sidebar.SubItem
  • Sidebar.Submenu
  • Sidebar.Footer
  • Sidebar.CollapseToggle
  • Sidebar.MenuSkeleton

A rail with sections, the current item and a collapse toggle.

import { House as HouseIcon, ListBullets as ListIcon } from "@phosphor-icons/react";
import { Sidebar } from '@usefragments/ui';

<Sidebar>
  <Sidebar.Header>Workspace</Sidebar.Header>
  <Sidebar.Nav>
    <Sidebar.Section label="Workspace">
      <Sidebar.Item icon={<HouseIcon />} href="/overview" active>Overview</Sidebar.Item>
      <Sidebar.Item icon={<ListIcon />} href="/findings">Findings</Sidebar.Item>
    </Sidebar.Section>
  </Sidebar.Nav>
  <Sidebar.Footer>
    <Sidebar.CollapseToggle />
  </Sidebar.Footer>
</Sidebar>

Examples

Collapsed

Collapsed to the glyphs; labels show in tooltips.

import { House as HouseIcon, ListBullets as ListIcon } from "@phosphor-icons/react";
import { Sidebar } from '@usefragments/ui';

<Sidebar defaultCollapsed>
  <Sidebar.Nav>
    <Sidebar.Section>

Row States

Badges, a working row and a disabled row.

import { ListBullets as ListIcon, Gear as GearIcon, ChartBar as ChartIcon } from "@phosphor-icons/react";
import { Sidebar } from '@usefragments/ui';

<Sidebar>
  <Sidebar.Nav>
    <Sidebar.Section label="Inbox">

With Submenu

A row opens a nested list.

import { Gear as GearIcon } from "@phosphor-icons/react";
import { Sidebar } from '@usefragments/ui';

<Sidebar>
  <Sidebar.Nav>
    <Sidebar.Section>

With Provider & Header Trigger

Sidebar.Provider holds the state so Header.Trigger can open the mobile panel.

import { House as HouseIcon } from "@phosphor-icons/react";
import { Header } from '@usefragments/ui';
import { Sidebar } from '@usefragments/ui';

<Sidebar.Provider collapsible="offcanvas">
  <Header>

render moves the row's look and props onto a router link.

import RouterLink from "next/link";
import { House as HouseIcon } from "@phosphor-icons/react";
import { Sidebar } from '@usefragments/ui';

<Sidebar>
  <Sidebar.Nav>

With Section Action

A collapsible section with one action beside its label.

import { Plus as PlusIcon, Folder as RepoIcon } from "@phosphor-icons/react";
import { Sidebar } from '@usefragments/ui';

<Sidebar>
  <Sidebar.Nav>
    <Sidebar.Section

With Loading Skeleton

Placeholder rows while navigation loads.

import { Sidebar } from '@usefragments/ui';

<Sidebar>
  <Sidebar.Nav>
    <Sidebar.Section label="Workspace">
      <Sidebar.MenuSkeleton count={4} />

API reference

Component props
Prop
Type
Default
Description
childrenRequiredReact.ReactNodeNot setSidebar content (use Sidebar.Header, Sidebar.Nav, Sidebar.Section, etc.)
collapsedboolean | undefinedNot setDesktop: the rail is collapsed (controlled). Ignored inside a Sidebar.Provider or AppShell.
CollapseToggle.aria-labelstring | undefinedNot setAccessible label. Defaults to "Collapse sidebar" or "Expand sidebar".
collapsibleSidebarCollapsible | undefinednoneiconoffcanvas"icon"How the rail collapses: to its glyphs (icon), off the canvas (offcanvas), or never (none)
defaultCollapsedboolean | undefinedfalseInitial collapsed state (uncontrolled)
defaultOpenboolean | undefinedfalseInitial open state (uncontrolled)
enableKeyboardShortcutboolean | undefinedtrueToggle with Cmd/Ctrl+B
Footer.childrenRequiredReact.ReactNodeNot set—
Header.childrenRequiredReact.ReactNodeNot set—
Header.collapsedContentReact.ReactNodeNot setShown instead of `children` while the rail is collapsed (a compact application glyph).
Item.activeboolean | undefinedNot setThe current page: wash and ring, `aria-current="page"`.
Item.badgeReact.ReactNodeNot setA trailing count or status. Plain content becomes a neutral Badge.
Item.childrenRequiredReact.ReactNodeNot set—
Item.defaultExpandedboolean | undefinedNot setThe submenu starts open (uncontrolled).
Item.disabledboolean | undefinedNot setThe row cannot be used.
Item.expandedboolean | undefinedNot setThe submenu is open (controlled).
Item.hasSubmenuboolean | undefinedNot setThe row opens a `Sidebar.Submenu` placed after it.
Item.hrefstring | undefinedNot setRenders a link.
Item.iconReact.ReactNodeNot setThe leading glyph. A collapsed rail shows only this.
Item.onClickReact.MouseEventHandler<HTMLElement> | undefinedNot setClick handler.
Item.onExpandedChange((expanded: boolean) => void) | undefinedNot setCalled when the submenu opens or closes.
Item.renderuseRender.RenderProp<Record<string, unknown>> | undefinedNot setReplace the rendered element, e.g. a router link: `render={<RouterLink to="/settings" />}`. The look and props move onto it.
Item.workingboolean | undefinedNot setAn agent is working in this row (the working wash and sweep, `aria-busy`).
MenuSkeleton.countnumber | undefinedNot setRows to draw.
MenuSkeleton.showIconboolean | undefinedNot setDraw a glyph box in each row.
Nav.aria-labelstring | undefinedNot setAccessible label for the navigation landmark.
Nav.childrenRequiredReact.ReactNodeNot set—
onCollapsedChange((collapsed: boolean) => void) | undefinedNot setCalled when collapsed state changes
onOpenChange((open: boolean) => void) | undefinedNot setCalled when open state changes
openboolean | undefinedNot setMobile: the panel is open (controlled)
Provider.childrenRequiredReact.ReactNodeNot set—
Provider.collapsedboolean | undefinedNot setDesktop: the rail is collapsed (controlled).
Provider.collapsibleSidebarCollapsible | undefinednoneiconoffcanvasNot setHow the rail collapses: to its glyphs (`icon`), off the canvas (`offcanvas`), or never.
Provider.defaultCollapsedboolean | undefinedNot setDesktop: the rail starts collapsed (uncontrolled).
Provider.defaultOpenboolean | undefinedNot setMobile: the panel starts open (uncontrolled).
Provider.enableKeyboardShortcutboolean | undefinedNot setToggle with Cmd/Ctrl+B.
Provider.onCollapsedChange((collapsed: boolean) => void) | undefinedNot setCalled when the rail collapses or expands.
Provider.onOpenChange((open: boolean) => void) | undefinedNot setCalled when the mobile panel opens or closes.
Provider.openboolean | undefinedNot setMobile: the panel is open (controlled).
Section.actionReact.ReactNodeNot setOne control beside the label, usually a `Sidebar.SectionAction`.
Section.childrenRequiredReact.ReactNodeNot set—
Section.collapsibleboolean | undefinedNot setThe label opens and closes the group.
Section.defaultOpenboolean | undefinedNot setA collapsible group starts open.
Section.labelstring | undefinedNot setThe group name above the rows.
SectionAction.aria-labelRequiredstringNot setRequired: the action is a glyph.
SectionAction.childrenRequiredReact.ReactNodeNot set—
SubItem.activeboolean | undefinedNot setThe current page.
SubItem.childrenRequiredReact.ReactNodeNot set—
SubItem.disabledboolean | undefinedNot setThe row cannot be used.
SubItem.hrefstring | undefinedNot setRenders a link.
SubItem.onClickReact.MouseEventHandler<HTMLElement> | undefinedNot setClick handler.
SubItem.renderuseRender.RenderProp<Record<string, unknown>> | undefinedNot setReplace the rendered element, e.g. a router link.
Submenu.childrenRequiredReact.ReactNodeNot set—
aria-labelstring | undefinedSidebar navigationAccessible label for the rail and the mobile panel

Accessibility

  • The rail is a labelled <aside>; Sidebar.Nav is a labelled <nav>
  • aria-current="page" on the current item
  • aria-expanded on items with submenus and on collapsible section labels
  • Below md the panel is a modal dialog: focus trap, a visible close, Escape closes, the scrim closes
  • Cmd/Ctrl+B toggles the sidebar
  • Collapsed rows keep their label for assistive tech and show it in a tooltip
  • Rows are at least 32px tall (the md control track) and the section action has a 32px hit area; under a coarse pointer both reach the 44px hit-area floor
  • AppShellparentAppShell.Sidebar wraps Sidebar and holds its state
  • HeadersiblingHeader.Trigger opens the mobile panel and brings back a hidden rail
  • DrawercompositionSidebar rows in a Drawer give a header-only app its mobile navigation
  • MenucompositionUse Menu for contextual actions within the sidebar

Next steps