Experience System

Sidebar

A composable application sidebar for persistent navigation, with collapsible desktop layouts and a mobile sheet.

Minimal collapsible icon-mode sidebar with a footer SidebarTrigger. The inset shows placeholder content without a header trigger.

Installation

The component is exported from @by/experience-system. Add the package with your package manager:

pnpm add @by/experience-system

In this monorepo, depend on the workspace package (for example via workspace:* or your catalog) so imports resolve to packages/experience-system.

Composition

Use the following composition to build a Sidebar layout:

SidebarProvider
├── Sidebar
│   ├── SidebarHeader (optional)
│   ├── SidebarContent
│   │   └── SidebarGroup
│   │       ├── SidebarGroupLabel (optional)
│   │       ├── SidebarGroupAction (optional)
│   │       ├── SidebarGroupContent
│   │       └── SidebarMenu
│   │           └── SidebarMenuItem
│   │               ├── SidebarMenuButton
│   │               ├── SidebarMenuAction (optional)
│   │               ├── SidebarMenuBadge (optional)
│   │               └── SidebarMenuSub (optional)
│   │                   └── SidebarMenuSubItem
│   │                       └── SidebarMenuSubButton
│   ├── SidebarFooter (optional)
│   └── SidebarRail (optional)
├── SidebarInset
└── SidebarTrigger (optional; often in header or footer)

Wrap the tree in SidebarProvider. Use SidebarInset for the main content region—required when variant="inset". On viewports below the desktop breakpoint, Sidebar renders as a sheet dialog; keep the layout inside ThemeProvider so the portal container resolves. Composition and behavior follow the shadcn/ui Sidebar pattern.

Usage

import {
  Sidebar,
  SidebarContent,
  SidebarGroup,
  SidebarGroupContent,
  SidebarInset,
  SidebarMenu,
  SidebarMenuButton,
  SidebarMenuItem,
  SidebarProvider,
  SidebarTrigger,
} from '@by/experience-system';

Sidebar and its parts are client components ('use client'). Use them inside a Client Component or a dynamic import when using the Next.js App Router.

<SidebarProvider>
  <Sidebar>
    <SidebarContent>
      <SidebarGroup>
        <SidebarGroupContent>
          <SidebarMenu>
            <SidebarMenuItem>
              <SidebarMenuButton isActive tooltip="Home">
                <HomeIcon />
                <span>Home</span>
              </SidebarMenuButton>
            </SidebarMenuItem>
          </SidebarMenu>
        </SidebarGroupContent>
      </SidebarGroup>
    </SidebarContent>
  </Sidebar>
  <SidebarInset>
    <header>
      <SidebarTrigger />
    </header>
    <main>{children}</main>
  </SidebarInset>
</SidebarProvider>

Use this comparison when choosing between Sidebar and Drawer.

AspectSidebarDrawer
PurposeExtends the workspace with additional information or controls.Temporarily displays supplementary content without changing the page layout.
Relationship to pagePart of the page layout.Overlay on top of the page.
PersistenceUsually remains available while the user works.Opened only when needed and dismissed afterwards.
Content visibilityUser is expected to view both the sidebar and the main content simultaneously.User focuses primarily on the drawer while still keeping some page context visible underneath.
Layout behaviorPushes or resizes the main content (depending on implementation).Does not affect page layout. Covers part of the content.
Interaction frequencyFrequent or continuous interaction throughout the workflow.Occasional or one-off interaction.
Mobile behaviorOften transforms into a drawer.Native interaction pattern on mobile.
StateCan remain open across navigation or during long sessions.Usually closes after completing or cancelling the task.
Typical exampleNavigation; filters adjusted repeatedly while exploring data; inspector / properties panel; selection details while editing; layers panel; file explorerCreating a new item; editing object details; viewing additional information; advanced settings; previewing an object; activity history; comments
Basic statementHiding this panel would significantly interrupt the user's workflow.User can complete this task and then continue working without needing the panel to remain visible.

When to use

Choose Sidebar when the panel is part of the layout, stays available during work, and hiding it would interrupt the workflow:

  • Application or MFE navigation with hierarchical routes, active states, and optional icon-only collapse on desktop.
  • Filters, inspector / properties panels, or selection details used repeatedly alongside the main content.
  • Layers panels, file explorers, and other tools that support continuous interaction throughout a session.

On mobile, Sidebar typically renders as a sheet—see Pin to dock to start from a Drawer and let users dock navigation when needed.

When not to use

  • Temporary tasks the user completes and dismisses (create item, edit details, one-off settings, comments)—use Drawer.
  • Simple confirmations or alerts—use Dialog.
  • Horizontal top-level site navigation without a vertical rail—use Navigation Menu or Menubar.

Examples

Overview

Default off-canvas sidebar with nested Collapsible sections, truncated labels, and a trigger in the inset header. Use variant="sidebar" (recommended). floating and inset exist for shadcn parity but are not recommended for product layouts.

Collapsible icon mode

Set collapsible="icon" so the desktop sidebar collapses to icons. Tooltips on SidebarMenuButton appear in icon mode; place SidebarTrigger placement="footer" in SidebarFooter to toggle open state (as in the top preview). Ctrl+B / Cmd+B also toggles the sidebar.

This sidebar collapses to icons. Click Ctrl+B to toggle.

Loading skeleton

Use SidebarMenuSkeleton with showIcon while async nav data loads.

Pin to dock

Pin navigation from a left Drawer (overlay; main content stays full width) into a docked Sidebar + SidebarInset layout (sidebar reserves width and shifts content). Place a pin control in the drawer header to dock; use PinOff in SidebarHeader to return to the drawer.

API Reference

The tables below mirror the shadcn/ui Sidebar API and the @by/experience-system implementation in packages/experience-system/src/components/Sidebar. Design-system extensions (RTL defaults, token styling, truncated-label tooltips) are called out inline.

SidebarProvider

Root wrapper. Provides sidebar context and sets width CSS variables on the provider element.

PropTypeDefault
defaultOpenbooleantrue
openboolean
onOpenChange(open: boolean) => void

Forwards standard div props (className, style, children, …).

Data attributeValues
data-slotsidebar-provider
data-state"expanded" | "collapsed"
CSS variableDescription
--sidebar-widthDesktop sidebar width (default 16rem).
--sidebar-width-iconCollapsed icon rail width (density-scaled).

Set --sidebar-width-mobile on Sidebar (mobile sheet) via style when overriding mobile width (default 18rem).

useSidebar

Hook to read and control sidebar state. Must be used inside SidebarProvider.

PropertyTypeDescription
state"expanded" | "collapsed"Desktop expanded/collapsed state.
openbooleanWhether the desktop sidebar is open.
setOpen(open: boolean) => voidSets desktop open state.
openMobilebooleanWhether the mobile sheet is open.
setOpenMobile(open: boolean) => voidSets mobile sheet open state.
isMobilebooleanViewport is below the mobile breakpoint (768px).
toggleSidebar() => voidToggles desktop or mobile sidebar.

Main sidebar panel. On mobile, renders as a Radix Dialog sheet.

PropTypeDefault
side"left" | "right"From ThemeProvider direction when omitted (LTR → left, RTL → right).
variant"sidebar" (recommended). "floating" and "inset" are supported for shadcn parity but not recommended for Blue Yonder product layouts."sidebar"
collapsible"offcanvas" | "icon" | "none""offcanvas"
dir"ltr" | "rtl"From ThemeProvider when omitted.

Forwards standard div props.

Data attributeValues
data-slotsidebar (panel), sidebar-container (desktop wrapper)
data-side"left" | "right"
data-state"expanded" | "collapsed" (desktop container)
data-collapsible"offcanvas" | "icon" when collapsed; empty when expanded
data-variant"sidebar" | "floating" | "inset"
data-mobile"true" on mobile sheet content

SidebarTrigger

Button that toggles the sidebar. Extends Button props (variant, size, className, …).

PropTypeDefault
placement"header" | "footer""footer"
(Button props)Button propsvariant="ghost", size="md", icon

placement: Footer (default) toggles ColumnShow / ColumnHide with RTL mirroring. Header uses LayoutSidebar on desktop and Menu on mobile; header icons are not mirrored in RTL. Stacks above SidebarRail (z-30 vs rail z-20) so the end-aligned footer trigger stays visible and clickable.

Data attributeValues
data-slotsidebar-trigger
data-sidebartrigger
data-placement"header" | "footer"

SidebarRail

Invisible edge control that toggles the sidebar on click.

PropTypeDefault
(button props)button HTML attributes
Data attributeValues
data-slotsidebar-rail
data-sidebarrail

SidebarInset

Main content region beside the sidebar. Renders a main element.

PropTypeDefault
(main props)main HTML attributes
Data attributeValues
data-slotsidebar-inset

SidebarHeader

Sticky header region inside Sidebar.

PropTypeDefault
(div props)div HTML attributes
Data attributeValues
data-slotsidebar-header
data-sidebarheader

SidebarFooter

Sticky footer region inside Sidebar.

PropTypeDefault
(div props)div HTML attributes
Data attributeValues
data-slotsidebar-footer
data-sidebarfooter

SidebarContent

Scrollable area between header and footer.

PropTypeDefault
(div props)div HTML attributes
Data attributeValues
data-slotsidebar-content
data-sidebarcontent

SidebarGroup

Section wrapper for a block of navigation.

PropTypeDefault
(div props)div HTML attributes
Data attributeValues
data-slotsidebar-group
data-sidebargroup

SidebarGroupLabel

Label for a SidebarGroup.

PropTypeDefault
asChildbooleanfalse
(div props)div HTML attributes
Data attributeValues
data-slotsidebar-group-label
data-sidebargroup-label

SidebarGroupAction

Action control aligned with a group label.

PropTypeDefault
asChildbooleanfalse
(button props)button HTML attributes
Data attributeValues
data-slotsidebar-group-action
data-sidebargroup-action

SidebarGroupContent

Content container under a group label.

PropTypeDefault
(div props)div HTML attributes
Data attributeValues
data-slotsidebar-group-content
data-sidebargroup-content

SidebarMenu

<ul> menu list inside a group.

PropTypeDefault
(ul props)ul HTML attributes
Data attributeValues
data-slotsidebar-menu
data-sidebarmenu

SidebarMenuItem

<li> wrapper for a menu row.

PropTypeDefault
(li props)li HTML attributes
Data attributeValues
data-slotsidebar-menu-item
data-sidebarmenu-item

SidebarMenuButton

Primary nav control in a menu item.

PropTypeDefault
asChildbooleanfalse
isActivebooleanfalse
isFolderbooleanfalse
variant"default" | "outline""default"
size"sm" | "md" | "lg""md"
tooltipstring | TooltipContent props
labelOverflow"truncate" | "wrap""truncate"
(button props)button HTML attributes

tooltip: Shows in collapsed icon mode; when expanded, wraps a trailing <span> label with overflow tooltip (SidebarTruncatedLabel) unless labelOverflow="wrap".

Data attributeValues
data-slotsidebar-menu-button
data-sidebarmenu-button
data-size"sm" | "md" | "lg"
data-activetrue when active
data-folderPresent when isFolder

SidebarMenuAction

Secondary action on a menu row (for example overflow menu trigger).

PropTypeDefault
asChildbooleanfalse
showOnHoverbooleanfalse
(button props)button HTML attributes
Data attributeValues
data-slotsidebar-menu-action
data-sidebarmenu-action

SidebarMenuBadge

Badge aligned to a menu item.

PropTypeDefault
(div props)div HTML attributes
Data attributeValues
data-slotsidebar-menu-badge
data-sidebarmenu-badge

SidebarMenuSkeleton

Placeholder row while nav data loads.

PropTypeDefault
showIconbooleanfalse
(div props)div HTML attributes
Data attributeValues
data-slotsidebar-menu-skeleton
data-sidebarmenu-skeleton, menu-skeleton-icon, menu-skeleton-text

SidebarMenuSub

Nested submenu list.

PropTypeDefault
(ul props)ul HTML attributes
Data attributeValues
data-slotsidebar-menu-sub
data-sidebarmenu-sub

SidebarMenuSubItem

<li> wrapper for a submenu row.

PropTypeDefault
(li props)li HTML attributes
Data attributeValues
data-slotsidebar-menu-sub-item
data-sidebarmenu-sub-item

SidebarMenuSubButton

Submenu link or button. Default element is <a> for leaf navigation (asChild + router link is preferred). When isFolder is set, renders <button type="button"> for collapsible section triggers (use with CollapsibleTrigger asChild).

PropTypeDefault
asChildbooleanfalse
size"sm" | "md""md"
isActivebooleanfalse
isFolderbooleanfalse
tooltipstring
labelOverflow"truncate" | "wrap""truncate"
(element props)a or button HTML attributes
Data attributeValues
data-slotsidebar-menu-sub-button
data-sidebarmenu-sub-button
data-size"sm" | "md"
data-activetrue when active
data-folderPresent when isFolder

SidebarSeparator

Horizontal rule between menu blocks.

PropTypeDefault
(div props)div HTML attributes
Data attributeValues
data-slotsidebar-separator
data-sidebarseparator

SidebarTruncatedLabel

Utility span that shows a tooltip when label text overflows. Used internally by menu buttons when tooltip is set.

PropTypeDefault
labelOverflow"truncate" | "wrap""truncate"
side"top" | "right" | "bottom" | "left"Reading-direction side
(span props)span HTML attributes

labelOverflow: truncate (default) ellipsizes overflowing text and shows a tooltip on hover. wrap breaks long labels across multiple lines with no overflow tooltip.

Data attributeValues
data-sidebarmenu-label

Accessibility

Use landmark and navigation semantics in the page shell (nav, aria-current on active routes via isActive styling). SidebarTrigger and SidebarRail expose Toggle Sidebar labels. On mobile, the sidebar sheet uses Radix Dialog focus management (Dialog accessibility).

Keyboard interactions

KeyDescription
Ctrl+B / Cmd+BToggles the sidebar (desktop open/collapse or mobile sheet).
Space / EnterActivates the focused menu button, link, or SidebarTrigger.
EscCloses the mobile sheet when open.

For collapsible sections inside the sidebar, see Collapsible keyboard interactions on CollapsibleTrigger. Nested branch rows must keep SidebarMenuSubItem (<li>) children inside SidebarMenuSub (<ul>). Folder sub-rows with isFolder render as <button> so aria-expanded from CollapsibleTrigger is valid.

Source in the repo: packages/experience-system/src/components/Sidebar/Sidebar.tsx. Agent-oriented contracts: packages/experience-system/src/components/Sidebar/Sidebar.instructions.md.