Skip to main content

Sidebar

Collapsible navigation panel that turns into a drawer on mobile.

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

Preview

<Sidebar>
  <Sidebar.Header>
    <Logo />
    <span>Acme App</span>
  </Sidebar.Header>
  <Sidebar.Nav>

Installation

npx @usefragments/cli add sidebar

Writes src/fragments/ui/components/Sidebar plus what it imports, the globals stylesheet, and .fragments/registry-lock.json, then prints the packages to add. No account, no key.

Your own registry: publish from Fragments Cloud and add --registry org/name. Connect a workspace.

Anatomy

Sidebar is a compound: import the root and reach its parts through dot notation.

  • Sidebar.Header
  • Sidebar.Nav
  • Sidebar.Section
  • Sidebar.SectionAction
  • Sidebar.Item
  • Sidebar.SubItem
  • Sidebar.Submenu
  • Sidebar.Footer
  • Sidebar.Trigger
  • Sidebar.Overlay
  • Sidebar.CollapseToggle
  • Sidebar.Rail
  • Sidebar.MenuSkeleton

Sections group items; active marks the current page.

<Sidebar>
  <Sidebar.Header>
    <Logo />
    <span>Acme App</span>
  </Sidebar.Header>
  <Sidebar.Nav>
    <Sidebar.Section>
      <Sidebar.Item icon={<HomeIcon />} active>Dashboard</Sidebar.Item>
      <Sidebar.Item icon={<ChartIcon />}>Analytics</Sidebar.Item>
      <Sidebar.Item icon={<UsersIcon />}>Team</Sidebar.Item>
      <Sidebar.Item icon={<FolderIcon />}>Projects</Sidebar.Item>
    </Sidebar.Section>
    <Sidebar.Section label="Settings">
      <Sidebar.Item icon={<GearIcon />}>Preferences</Sidebar.Item>
      <Sidebar.Item icon={<HelpIcon />}>Help</Sidebar.Item>
    </Sidebar.Section>
  </Sidebar.Nav>
  <Sidebar.Footer>

Examples

Collapsed

Icon-only rail; labels move into hover tooltips.

function App() {
  const [collapsed, setCollapsed] = useState(true);

  return (
    <Sidebar collapsed={collapsed} onCollapsedChange={setCollapsed}>
      <Sidebar.Header collapsedContent={<Logo />}>

With Badges

Trailing counts for unread or pending work.

<Sidebar>
  <Sidebar.Nav>
    <Sidebar.Section>
      <Sidebar.Item icon={<HomeIcon />} active>Dashboard</Sidebar.Item>
      <Sidebar.Item icon={<ChartIcon />} badge="3">Analytics</Sidebar.Item>
      <Sidebar.Item icon={<UsersIcon />} badge="12">Team</Sidebar.Item>

With Submenu

Nested items; defaultExpanded sets the opening state.

<Sidebar>
  <Sidebar.Nav>
    <Sidebar.Section>
      <Sidebar.Item icon={<HomeIcon />}>Dashboard</Sidebar.Item>
      {/* Use defaultExpanded for uncontrolled mode - no state needed! */}
      <Sidebar.Item icon={<FolderIcon />} hasSubmenu defaultExpanded>

With Disabled Items

Items switched off by permissions or feature flags.

<Sidebar>
  <Sidebar.Nav>
    <Sidebar.Section>
      <Sidebar.Item icon={<HomeIcon />} active>Dashboard</Sidebar.Item>
      <Sidebar.Item icon={<ChartIcon />}>Analytics</Sidebar.Item>
      <Sidebar.Item icon={<UsersIcon />} disabled>

With Provider & External Trigger

useSidebar drives the sidebar from anywhere, plus Cmd/Ctrl+B.

function App() {
  return (
    <SidebarProvider>
      <Stack direction="row" gap="lg" align="start">
        <Sidebar>
          <Sidebar.Header>

With asChild (Polymorphic)

Render items as your router's link component.

import Link from 'next/link';

<Sidebar>
  <Sidebar.Nav>
    <Sidebar.Section>
      <Sidebar.Item icon={<HomeIcon />} active asChild>

With Section Action

A section header can carry one quick action.

<Sidebar>
  <Sidebar.Nav>
    <Sidebar.Section
      label="Projects"
      action={
        <Sidebar.SectionAction aria-label="Add project" onClick={handleAdd}>

With Loading Skeleton

Placeholder rows to show while navigation data loads.

<Sidebar>
  <Sidebar.Header>
    <Logo />
    <span>Acme App</span>
  </Sidebar.Header>
  <Sidebar.Nav>

With Rail Toggle

Drag-handle toggle on the sidebar edge — hover to reveal, click to collapse.

function App() {
  const [collapsed, setCollapsed] = useState(false);

  return (
    <Sidebar collapsed={collapsed} onCollapsedChange={setCollapsed}>
      <Sidebar.Header collapsedContent={<Logo />}>

Offcanvas Collapsed

Hides the panel entirely, leaving a floating re-open button.

function App() {
  const [collapsed, setCollapsed] = useState(true);

  return (
    <Sidebar collapsed={collapsed} onCollapsedChange={setCollapsed} collapsible="offcanvas">
      <Sidebar.Header collapsedContent={<Logo />}>

API

Component props
Prop
Type
Default
Description
childrenRequirednodeNot setSidebar content (use Sidebar.Header, Sidebar.Nav, Sidebar.Section, etc.)
collapsedbooleanNot setIcon-only mode for desktop (controlled)
defaultCollapsedbooleanfalseInitial collapsed state (uncontrolled)
onCollapsedChangefunctionNot setCalled when collapsed state changes
openbooleanNot setMobile drawer open state (controlled)
defaultOpenbooleanfalseInitial open state (uncontrolled)
onOpenChangefunctionNot setCalled when open state changes
widthstring240pxWidth of expanded sidebar
collapsedWidthstringvar(--fui-navigation-sidebar-collapsed-width, 56px)Width when collapsed
positionenumleftrightleftSidebar position
collapsibleenumiconoffcanvasnoneiconCollapse behavior mode
activeIndicatorenumstartendstartPlacement of the active-item affordance
styleobjectNot set

Accessibility

  • Uses semantic <nav> element with aria-label
  • aria-current="page" on active items
  • aria-expanded on items with submenus
  • Escape key closes mobile drawer
  • Cmd/Ctrl+B keyboard shortcut toggles sidebar (when using SidebarProvider)
  • Focus trap in mobile drawer mode
  • Minimum 44px touch targets

Guidance

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 a list of links)
  • TabsalternativeUse Tabs for in-page section navigation
  • MenucompositionUse Menu for contextual actions within sidebar

Next steps