# Sidebar

A collapsible app sidebar that renders as a sheet on small screens.

```tsx
"use client"

import type { ElementType } from "react"
import { useState } from "react"
import * as stylex from "@stylexjs/stylex"
import { Avatar, AvatarFallback, AvatarImage } from "@/components/ui/avatar"
import {
  Breadcrumb,
  BreadcrumbItem,
  BreadcrumbLink,
  BreadcrumbList,
  BreadcrumbPage,
  BreadcrumbSeparator,
} from "@/components/ui/breadcrumb"
import {
  Collapsible,
  CollapsibleContent,
  CollapsibleTrigger,
  collapsibleStyles,
} from "@/components/ui/collapsible"
import {
  DropdownMenu,
  DropdownMenuContent,
  DropdownMenuGroup,
  DropdownMenuItem,
  DropdownMenuLabel,
  DropdownMenuSeparator,
  DropdownMenuShortcut,
  DropdownMenuTrigger,
} from "@/components/ui/dropdown-menu"
import { Separator } from "@/components/ui/separator"
import {
  Sidebar,
  SidebarContent,
  SidebarFooter,
  SidebarGroup,
  SidebarGroupLabel,
  SidebarHeader,
  SidebarInset,
  SidebarMenu,
  SidebarMenuAction,
  SidebarMenuButton,
  SidebarMenuItem,
  SidebarMenuSub,
  SidebarMenuSubButton,
  SidebarMenuSubItem,
  SidebarProvider,
  SidebarRail,
  SidebarTrigger,
  sidebarStyles,
  useSidebar,
} from "@/components/ui/sidebar"
import { HStack, VStack } from "@/components/ui/stack"
import { breakpoints } from "@/theme/breakpoints.stylex"
import { color, leading, radius, space, text } from "@/theme/tokens.stylex"
import {
  AudioWaveform,
  BadgeCheck,
  Bell,
  BookOpen,
  Bot,
  ChevronRight,
  ChevronsUpDown,
  Command,
  CreditCard,
  Folder,
  Forward,
  Frame,
  GalleryVerticalEnd,
  LogOut,
  Map,
  MoreHorizontal,
  PieChart,
  Plus,
  Settings2,
  Sparkles,
  SquareTerminal,
  Trash2,
} from "lucide-react"

const sidebarPrimary = "light-dark(oklch(0.205 0 0), oklch(0.922 0 0))"
const sidebarPrimaryForeground = "light-dark(oklch(0.985 0 0), oklch(0.205 0 0))"

const styles = stylex.create({
  logo: {
    display: "flex",
    aspectRatio: "1",
    width: space.s8,
    height: space.s8,
    flexShrink: 0,
    alignItems: "center",
    justifyContent: "center",
    borderRadius: radius.lg,
    backgroundColor: sidebarPrimary,
    color: sidebarPrimaryForeground,
  },
  lines: { display: "grid", flexGrow: 1, minWidth: 0, textAlign: "left" },
  title: { fontWeight: 500 },
  detail: { fontSize: text.xs, lineHeight: leading.xs },
  trailing: { marginInlineStart: "auto" },
  menu: { minWidth: "14rem" },
  teamLogo: {
    display: "flex",
    width: space.s6,
    height: space.s6,
    flexShrink: 0,
    alignItems: "center",
    justifyContent: "center",
    borderRadius: radius.md,
    borderWidth: 1,
    borderStyle: "solid",
    "--carbon-icon-size": space.s3_5,
  },
  muted: { color: color.mutedForeground },
  header: { height: space.s16, flexShrink: 0 },
  headerRule: { height: space.s4 },
  wideOnly: { display: { default: "none", [breakpoints.md]: "inline-flex" } },
  grid: {
    display: "grid",
    gap: space.s4,
    gridTemplateColumns: { default: "1fr", [breakpoints.md]: "repeat(3, 1fr)" },
  },
  tile: {
    aspectRatio: "16 / 9",
    borderRadius: radius.xl,
    backgroundColor: `color-mix(in oklab, ${color.muted} 50%, transparent)`,
  },
  canvas: {
    flexGrow: 1,
    borderRadius: radius.xl,
    backgroundColor: `color-mix(in oklab, ${color.muted} 50%, transparent)`,
  },
})

const data = {
  user: { name: "shadcn", email: "m@example.com", avatar: "https://github.com/shadcn.png" },
  teams: [
    { name: "Acme Inc", logo: GalleryVerticalEnd, plan: "Enterprise" },
    { name: "Acme Corp.", logo: AudioWaveform, plan: "Startup" },
    { name: "Evil Corp.", logo: Command, plan: "Free" },
  ],
  navMain: [
    {
      title: "Playground",
      url: "#",
      icon: SquareTerminal,
      isActive: true,
      items: [
        { title: "History", url: "#", isActive: true },
        { title: "Starred", url: "#" },
        { title: "Settings", url: "#" },
      ],
    },
    {
      title: "Models",
      url: "#",
      icon: Bot,
      items: [
        { title: "Genesis", url: "#" },
        { title: "Explorer", url: "#" },
        { title: "Quantum", url: "#" },
      ],
    },
    {
      title: "Documentation",
      url: "#",
      icon: BookOpen,
      items: [
        { title: "Introduction", url: "#" },
        { title: "Get Started", url: "#" },
        { title: "Tutorials", url: "#" },
        { title: "Changelog", url: "#" },
      ],
    },
    {
      title: "Settings",
      url: "#",
      icon: Settings2,
      items: [
        { title: "General", url: "#" },
        { title: "Team", url: "#" },
        { title: "Billing", url: "#" },
        { title: "Limits", url: "#" },
      ],
    },
  ],
  projects: [
    { name: "Design Engineering", url: "#", icon: Frame },
    { name: "Sales & Marketing", url: "#", icon: PieChart },
    { name: "Travel", url: "#", icon: Map },
  ],
}

function TeamSwitcher({ teams }: { teams: { name: string; logo: ElementType; plan: string }[] }) {
  const { isMobile } = useSidebar()
  const [activeTeam, setActiveTeam] = useState(teams[0])

  if (!activeTeam) return null

  return (
    <SidebarMenu>
      <SidebarMenuItem>
        <DropdownMenu>
          <DropdownMenuTrigger render={<SidebarMenuButton size="lg" />}>
            <div {...stylex.props(styles.logo)}>
              <activeTeam.logo />
            </div>
            <div {...stylex.props(styles.lines)}>
              <span {...stylex.props(sidebarStyles.truncate, styles.title)}>{activeTeam.name}</span>
              <span {...stylex.props(sidebarStyles.truncate, styles.detail)}>
                {activeTeam.plan}
              </span>
            </div>
            <ChevronsUpDown {...stylex.props(styles.trailing)} />
          </DropdownMenuTrigger>
          <DropdownMenuContent
            xstyle={styles.menu}
            align="start"
            side={isMobile ? "bottom" : "right"}
            sideOffset={4}
          >
            <DropdownMenuGroup>
              <DropdownMenuLabel>Teams</DropdownMenuLabel>
              {teams.map((team, index) => (
                <DropdownMenuItem key={team.name} onClick={() => setActiveTeam(team)}>
                  <div {...stylex.props(styles.teamLogo)}>
                    <team.logo />
                  </div>
                  {team.name}
                  <DropdownMenuShortcut>⌘{index + 1}</DropdownMenuShortcut>
                </DropdownMenuItem>
              ))}
            </DropdownMenuGroup>
            <DropdownMenuSeparator />
            <DropdownMenuGroup>
              <DropdownMenuItem>
                <div {...stylex.props(styles.teamLogo)}>
                  <Plus />
                </div>
                <span {...stylex.props(styles.title, styles.muted)}>Add team</span>
              </DropdownMenuItem>
            </DropdownMenuGroup>
          </DropdownMenuContent>
        </DropdownMenu>
      </SidebarMenuItem>
    </SidebarMenu>
  )
}

function NavMain({
  items,
}: {
  items: {
    title: string
    url: string
    icon?: ElementType
    isActive?: boolean
    items?: { title: string; url: string; isActive?: boolean }[]
  }[]
}) {
  return (
    <SidebarGroup>
      <SidebarGroupLabel>Platform</SidebarGroupLabel>
      <SidebarMenu>
        {items.map((item) => (
          <Collapsible key={item.title} defaultOpen={item.isActive}>
            <SidebarMenuItem>
              <CollapsibleTrigger render={<SidebarMenuButton tooltip={item.title} />}>
                {item.icon && <item.icon />}
                <span {...stylex.props(sidebarStyles.truncate)}>{item.title}</span>
                <ChevronRight {...stylex.props(collapsibleStyles.chevronInline, styles.trailing)} />
              </CollapsibleTrigger>
              <CollapsibleContent>
                <SidebarMenuSub>
                  {item.items?.map((subItem) => (
                    <SidebarMenuSubItem key={subItem.title}>
                      <SidebarMenuSubButton
                        render={<a href={subItem.url} />}
                        isActive={subItem.isActive}
                      >
                        <span {...stylex.props(sidebarStyles.truncate)}>{subItem.title}</span>
                      </SidebarMenuSubButton>
                    </SidebarMenuSubItem>
                  ))}
                </SidebarMenuSub>
              </CollapsibleContent>
            </SidebarMenuItem>
          </Collapsible>
        ))}
      </SidebarMenu>
    </SidebarGroup>
  )
}

function NavProjects({
  projects,
}: {
  projects: { name: string; url: string; icon: ElementType }[]
}) {
  const { isMobile } = useSidebar()

  return (
    <SidebarGroup>
      <SidebarGroupLabel>Projects</SidebarGroupLabel>
      <SidebarMenu>
        {projects.map((item) => (
          <SidebarMenuItem key={item.name}>
            <SidebarMenuButton render={<a href={item.url} />}>
              <item.icon />
              <span {...stylex.props(sidebarStyles.truncate)}>{item.name}</span>
            </SidebarMenuButton>
            <DropdownMenu>
              <DropdownMenuTrigger render={<SidebarMenuAction showOnHover aria-label="More" />}>
                <MoreHorizontal />
              </DropdownMenuTrigger>
              <DropdownMenuContent
                side={isMobile ? "bottom" : "right"}
                align={isMobile ? "end" : "start"}
              >
                <DropdownMenuItem>
                  <Folder {...stylex.props(styles.muted)} />
                  <span>View Project</span>
                </DropdownMenuItem>
                <DropdownMenuItem>
                  <Forward {...stylex.props(styles.muted)} />
                  <span>Share Project</span>
                </DropdownMenuItem>
                <DropdownMenuSeparator />
                <DropdownMenuItem>
                  <Trash2 {...stylex.props(styles.muted)} />
                  <span>Delete Project</span>
                </DropdownMenuItem>
              </DropdownMenuContent>
            </DropdownMenu>
          </SidebarMenuItem>
        ))}
        <SidebarMenuItem>
          <SidebarMenuButton xstyle={styles.muted}>
            <MoreHorizontal />
            <span>More</span>
          </SidebarMenuButton>
        </SidebarMenuItem>
      </SidebarMenu>
    </SidebarGroup>
  )
}

function NavUser({ user }: { user: { name: string; email: string; avatar: string } }) {
  const { isMobile } = useSidebar()

  return (
    <SidebarMenu>
      <SidebarMenuItem>
        <DropdownMenu>
          <DropdownMenuTrigger render={<SidebarMenuButton size="lg" />}>
            <Avatar>
              <AvatarImage src={user.avatar} alt={user.name} />
              <AvatarFallback>CN</AvatarFallback>
            </Avatar>
            <div {...stylex.props(styles.lines)}>
              <span {...stylex.props(sidebarStyles.truncate, styles.title)}>{user.name}</span>
              <span {...stylex.props(sidebarStyles.truncate, styles.detail)}>{user.email}</span>
            </div>
            <ChevronsUpDown {...stylex.props(styles.trailing)} />
          </DropdownMenuTrigger>
          <DropdownMenuContent
            xstyle={styles.menu}
            side={isMobile ? "bottom" : "right"}
            align="end"
            sideOffset={4}
          >
            <DropdownMenuGroup>
              <DropdownMenuLabel>
                <HStack gap={2} align="center">
                  <Avatar>
                    <AvatarImage src={user.avatar} alt={user.name} />
                    <AvatarFallback>CN</AvatarFallback>
                  </Avatar>
                  <div {...stylex.props(styles.lines)}>
                    <span {...stylex.props(sidebarStyles.truncate, styles.title)}>{user.name}</span>
                    <span {...stylex.props(sidebarStyles.truncate, styles.detail)}>
                      {user.email}
                    </span>
                  </div>
                </HStack>
              </DropdownMenuLabel>
            </DropdownMenuGroup>
            <DropdownMenuSeparator />
            <DropdownMenuGroup>
              <DropdownMenuItem>
                <Sparkles />
                Upgrade to Pro
              </DropdownMenuItem>
            </DropdownMenuGroup>
            <DropdownMenuSeparator />
            <DropdownMenuGroup>
              <DropdownMenuItem>
                <BadgeCheck />
                Account
              </DropdownMenuItem>
              <DropdownMenuItem>
                <CreditCard />
                Billing
              </DropdownMenuItem>
              <DropdownMenuItem>
                <Bell />
                Notifications
              </DropdownMenuItem>
            </DropdownMenuGroup>
            <DropdownMenuSeparator />
            <DropdownMenuGroup>
              <DropdownMenuItem>
                <LogOut />
                Log out
              </DropdownMenuItem>
            </DropdownMenuGroup>
          </DropdownMenuContent>
        </DropdownMenu>
      </SidebarMenuItem>
    </SidebarMenu>
  )
}

export default function SidebarDemo() {
  return (
    <SidebarProvider>
      <Sidebar collapsible="icon">
        <SidebarHeader>
          <TeamSwitcher teams={data.teams} />
        </SidebarHeader>
        <SidebarContent>
          <NavMain items={data.navMain} />
          <NavProjects projects={data.projects} />
        </SidebarContent>
        <SidebarFooter>
          <NavUser user={data.user} />
        </SidebarFooter>
        <SidebarRail />
      </Sidebar>
      <SidebarInset>
        <HStack render={<header />} gap={2} align="center" paddingInline={4} xstyle={styles.header}>
          <SidebarTrigger />
          <Separator orientation="vertical" xstyle={styles.headerRule} />
          <Breadcrumb>
            <BreadcrumbList>
              <BreadcrumbItem xstyle={styles.wideOnly}>
                <BreadcrumbLink href="#">Build Your Application</BreadcrumbLink>
              </BreadcrumbItem>
              <BreadcrumbSeparator xstyle={styles.wideOnly} />
              <BreadcrumbItem>
                <BreadcrumbPage>Data Fetching</BreadcrumbPage>
              </BreadcrumbItem>
            </BreadcrumbList>
          </Breadcrumb>
        </HStack>
        <VStack gap={4} grow paddingInline={4} paddingBlockEnd={4}>
          <div {...stylex.props(styles.grid)}>
            <div {...stylex.props(styles.tile)} />
            <div {...stylex.props(styles.tile)} />
            <div {...stylex.props(styles.tile)} />
          </div>
          <div {...stylex.props(styles.canvas)} />
        </VStack>
      </SidebarInset>
    </SidebarProvider>
  )
}
```

## Installation

1. Install the following dependencies:

   ```bash
   npm install @base-ui/react @stylexjs/stylex clsx lucide-react
   ```

2. Set up the token layer and the StyleX compiler, if you have not already. See
   [Installation](https://carbon.style/docs/installation). The sidebar also needs `interop.css` for the
   open-menu states and the `no-scrollbar` hook, the `sidebarMenuItem` marker from
   `markers.stylex.ts` on the same page, and the other components it renders:
   [Button](https://carbon.style/docs/components/button), [Input](https://carbon.style/docs/components/input),
   [Separator](https://carbon.style/docs/components/separator), [Sheet](https://carbon.style/docs/components/sheet),
   [Skeleton](https://carbon.style/docs/components/skeleton), [Stack](https://carbon.style/docs/components/stack) and
   [Tooltip](https://carbon.style/docs/components/tooltip).

3. Copy and paste the following code into your project.

   ```tsx title="components/ui/sidebar.tsx"
   "use client"

   import type { ComponentProps } from "react"
   import {
     createContext,
     use,
     useCallback,
     useEffect,
     useId,
     useLayoutEffect,
     useMemo,
     useRef,
     useState,
     useSyncExternalStore,
   } from "react"
   import { useRender } from "@base-ui/react/use-render"
   import * as stylex from "@stylexjs/stylex"
   import type { StyleXStyles } from "@stylexjs/stylex"
   import { clsx } from "clsx"
   import { PanelLeft } from "lucide-react"

   import { Button } from "@/components/ui/button"
   import { Input } from "@/components/ui/input"
   import { Separator } from "@/components/ui/separator"
   import { Sheet, SheetContent, SheetDescription, SheetHeader, SheetTitle } from "@/components/ui/sheet"
   import { Skeleton } from "@/components/ui/skeleton"
   import { VStack } from "@/components/ui/stack"
   import { Tooltip, TooltipContent, TooltipTrigger } from "@/components/ui/tooltip"
   import { breakpoints } from "@/theme/breakpoints.stylex"
   import { sidebarMenuItem } from "@/theme/markers.stylex"
   import { states } from "@/theme/states"
   import {
     color,
     duration,
     ease,
     layer,
     leading,
     radius,
     shadow,
     space,
     text,
   } from "@/theme/tokens.stylex"

   const sidebarForeground = "light-dark(oklch(0.145 0 0), oklch(0.985 0 0))"
   const sidebarAccent = "light-dark(oklch(0.95 0 0), oklch(0.269 0 0))"
   const sidebarAccentForeground = "light-dark(oklch(0.205 0 0), oklch(0.985 0 0))"
   const sidebarBorder = "light-dark(oklch(0.922 0 0), oklch(1 0 0 / 10%))"

   const SIDEBAR_COOKIE_NAME = "carbon-sidebar"
   const SIDEBAR_COOKIE_MAX_AGE = 60 * 60 * 24 * 7
   const SIDEBAR_KEYBOARD_SHORTCUT = "b"
   const SIDEBAR_PEEK_CLOSE_DELAY = 250

   const MOBILE_QUERY = "(width < 48rem)"

   type SidebarSide = "left" | "right"
   type SidebarVariant = "sidebar" | "floating" | "inset"
   type SidebarCollapsible = "offcanvas" | "icon" | "none"
   type SidebarState = "expanded" | "collapsed"

   type SidebarContextValue = {
     state: SidebarState
     open: boolean
     setOpen: (open: boolean) => void
     openMobile: boolean
     setOpenMobile: (open: boolean) => void
     isMobile: boolean
     toggleSidebar: () => void
     variant: SidebarVariant
     peeking: boolean
     setPeeking: (peeking: boolean) => void
     instant: boolean
   }

   const SidebarContext = createContext<SidebarContextValue | null>(null)

   const SidebarPanelContext = createContext<{
     side: SidebarSide
     collapsible: SidebarCollapsible
     iconMode: boolean
   }>({ side: "left", collapsible: "offcanvas", iconMode: false })

   const SidebarMenuItemContext = createContext<{
     size: SidebarMenuButtonSize
     setSize: (size: SidebarMenuButtonSize) => void
     hasAction: boolean
     setHasAction: (hasAction: boolean) => void
   } | null>(null)

   const styles = stylex.create({
     wrapper: {
       display: "flex",
       minHeight: "100svh",
       width: "100%",
       "--carbon-sidebar-width": "16rem",
       "--carbon-sidebar-width-icon": "3rem",
     },
     wrapperInset: { backgroundColor: color.tray },
     root: {
       display: { default: "none", [breakpoints.md]: "block" },
       color: sidebarForeground,
     },
     gap: {
       position: "relative",
       width: "var(--carbon-sidebar-width)",
       backgroundColor: color.transparent,
       transitionProperty: "width",
       transitionDuration: {
         default: duration.panel,
         "@media (prefers-reduced-motion: reduce)": "0s",
       },
       transitionTimingFunction: ease.spring,
     },
     gapOffcanvas: { width: 0 },
     instant: { transitionDuration: "0s" },
     gapIcon: { width: "var(--carbon-sidebar-width-icon)" },
     gapIconPadded: { width: `calc(var(--carbon-sidebar-width-icon) + ${space.s4})` },
     container: {
       position: "fixed",
       insetBlock: 0,
       zIndex: layer.overlay,
       display: "flex",
       width: "var(--carbon-sidebar-width)",
       transitionProperty: "left, right, width",
       transitionDuration: {
         default: duration.panel,
         "@media (prefers-reduced-motion: reduce)": "0s",
       },
       transitionTimingFunction: ease.spring,
     },
     containerLeft: { left: 0 },
     containerRight: { right: 0 },
     containerLeftOffcanvas: { left: "calc(-1 * var(--carbon-sidebar-width))" },
     containerRightOffcanvas: { right: "calc(-1 * var(--carbon-sidebar-width))" },
     containerPeekLeft: { boxShadow: shadow.peekLeft },
     containerPeekRight: { boxShadow: shadow.peekRight },
     containerIcon: { width: "var(--carbon-sidebar-width-icon)" },
     containerIconPadded: { width: `calc(var(--carbon-sidebar-width-icon) + ${space.s4} + 2px)` },
     containerPadded: { padding: space.s2 },
     containerBorderRight: {
       borderRightWidth: 1,
       borderRightStyle: "solid",
       borderRightColor: sidebarBorder,
     },
     containerBorderLeft: {
       borderLeftWidth: 1,
       borderLeftStyle: "solid",
       borderLeftColor: sidebarBorder,
     },
     inner: {
       display: "flex",
       width: "100%",
       height: "100%",
       flexDirection: "column",
       backgroundColor: color.sidebar,
     },
     innerFloating: {
       borderRadius: radius.lg,
       boxShadow: shadow.raised,
     },
     innerInset: { backgroundColor: color.transparent },
     none: {
       display: "flex",
       height: "100%",
       width: "var(--carbon-sidebar-width)",
       flexDirection: "column",
       backgroundColor: color.sidebar,
       color: sidebarForeground,
     },
     sheet: {
       width: "18rem",
       padding: 0,
       gap: 0,
       backgroundColor: color.sidebar,
       color: sidebarForeground,
       "--carbon-sidebar-width": "18rem",
     },
     sheetBody: { display: "flex", height: "100%", width: "100%", flexDirection: "column" },
     rail: {
       position: "absolute",
       insetBlock: 0,
       zIndex: layer.sticky,
       display: { default: "none", [breakpoints.sm]: "flex" },
       width: space.s4,
       transform: "translateX(-50%)",
       transitionProperty: "background-color, left, right, transform",
       transitionDuration: {
         default: `${duration.color}, ${duration.panel}, ${duration.panel}, ${duration.panel}`,
         "@media (prefers-reduced-motion: reduce)": "0s",
       },
       transitionTimingFunction: `${ease.standard}, ${ease.spring}, ${ease.spring}, ${ease.spring}`,
       "::after": {
         content: "''",
         position: "absolute",
         insetBlock: 0,
         left: "50%",
         width: 2,
         backgroundColor: { default: null, ":hover": sidebarBorder },
       },
     },
     railLeft: { right: `calc(-1 * ${space.s4})`, cursor: "w-resize" },
     railRight: { left: 0, cursor: "e-resize" },
     railLeftCollapsed: { cursor: "e-resize" },
     railRightCollapsed: { cursor: "w-resize" },
     railOffcanvas: {
       transform: "translateX(0)",
       backgroundColor: { default: null, ":hover": color.sidebar },
       "::after": { left: "100%" },
     },
     railLeftOffcanvas: { right: `calc(-1 * ${space.s2})` },
     railRightOffcanvas: { left: `calc(-1 * ${space.s2})` },
     inset: {
       position: "relative",
       display: "flex",
       width: "100%",
       flexGrow: 1,
       flexDirection: "column",
       backgroundColor: color.background,
     },
     insetVariant: {
       margin: { default: null, [breakpoints.md]: space.s2 },
       marginInlineStart: { default: null, [breakpoints.md]: 0 },
       borderRadius: { default: null, [breakpoints.md]: radius.xl },
       backgroundColor: { default: color.background, [breakpoints.md]: color.card },
       boxShadow: { default: null, [breakpoints.md]: shadow.raised },
     },
     insetVariantCollapsed: { marginInlineStart: { default: null, [breakpoints.md]: space.s2 } },
     input: { backgroundColor: { default: color.background, ":disabled": color.inputDisabledBg } },
     separator: { marginInline: space.s2, width: "auto", backgroundColor: sidebarBorder },
     contentIcon: { overflow: "hidden" },
     group: { position: "relative", width: "100%" },
     groupLabel: {
       display: "flex",
       height: space.s8,
       flexShrink: 0,
       alignItems: "center",
       borderRadius: radius.md,
       paddingInline: space.s2,
       fontSize: text.xs,
       lineHeight: leading.xs,
       fontWeight: 500,
       color: `color-mix(in oklab, ${sidebarForeground} 70%, transparent)`,
       transitionProperty: "margin, opacity",
       transitionDuration: {
         default: duration.panel,
         "@media (prefers-reduced-motion: reduce)": "0s",
       },
       transitionTimingFunction: ease.spring,
       "--carbon-icon-size": space.s4,
     },
     groupLabelIcon: { marginBlockStart: `calc(-1 * ${space.s8})`, opacity: 0 },
     groupAction: {
       position: "absolute",
       top: space.s3_5,
       right: space.s3,
       display: "flex",
       aspectRatio: "1",
       width: space.s5,
       alignItems: "center",
       justifyContent: "center",
       borderRadius: radius.md,
       padding: 0,
       color: { default: sidebarForeground, ":hover": sidebarAccentForeground },
       backgroundColor: { default: null, ":hover": color.itemHighlightBg },
       transitionProperty: "color, background-color, box-shadow",
       transitionDuration: duration.color,
       transitionTimingFunction: ease.standard,
       "--carbon-icon-size": space.s4,
       "::after": {
         content: "''",
         position: "absolute",
         inset: `calc(-1 * ${space.s2})`,
         display: { default: "block", [breakpoints.md]: "none" },
       },
     },
     groupContent: { width: "100%", fontSize: text.sm, lineHeight: leading.sm },
     menu: { width: "100%" },
     menuItem: { position: "relative" },
     menuButton: {
       display: "flex",
       width: "100%",
       alignItems: "center",
       gap: space.s2,
       overflow: "hidden",
       borderRadius: radius.md,
       padding: space.s2,
       textAlign: "left",
       fontSize: text.sm,
       lineHeight: leading.sm,
       backgroundColor: {
         default: null,
         ":hover": color.itemHighlightBg,
         ":active": color.itemHighlightBg,
       },
       color: {
         default: null,
         ":hover": sidebarAccentForeground,
         ":active": sidebarAccentForeground,
       },
       transitionProperty: "width, height, padding, color, background-color, box-shadow",
       transitionDuration: {
         default: duration.color,
         "@media (prefers-reduced-motion: reduce)": "0s",
       },
       transitionTimingFunction: ease.standard,
       pointerEvents: { default: null, ":disabled": "none" },
       opacity: { default: 1, ":disabled": 0.5 },
       "--carbon-icon-size": space.s4,
       "--carbon-menu-open-bg": color.itemHighlightBg,
       "--carbon-menu-open-fg": sidebarAccentForeground,
     },
     rowActive: {
       backgroundColor: sidebarAccent,
       color: sidebarAccentForeground,
     },
     rowActiveLabel: {
       WebkitTextStrokeWidth: "0.3px",
       WebkitTextStrokeColor: "currentColor",
     },
     menuButtonWithAction: { paddingInlineEnd: space.s8 },
     menuButtonIcon: { width: space.s8, height: space.s8, padding: space.s2 },
     menuButtonIconLg: { padding: 0 },
     menuAction: {
       position: "absolute",
       right: space.s1,
       display: "flex",
       aspectRatio: "1",
       width: space.s5,
       alignItems: "center",
       justifyContent: "center",
       borderRadius: radius.md,
       padding: 0,
       color: {
         default: sidebarForeground,
         ":hover": sidebarAccentForeground,
         [stylex.when.ancestor(":hover", sidebarMenuItem)]: sidebarAccentForeground,
       },
       backgroundColor: { default: null, ":hover": color.itemHighlightBg },
       transitionProperty: "color, background-color, box-shadow, opacity",
       transitionDuration: duration.color,
       transitionTimingFunction: ease.standard,
       "--carbon-icon-size": space.s4,
       "::after": {
         content: "''",
         position: "absolute",
         inset: `calc(-1 * ${space.s2})`,
         display: { default: "block", [breakpoints.md]: "none" },
       },
     },
     menuActionShowOnHover: {
       opacity: {
         default: 1,
         [breakpoints.md]: {
           default: 0,
           [stylex.when.ancestor(":hover", sidebarMenuItem)]: 1,
           [stylex.when.ancestor(":focus-within", sidebarMenuItem)]: 1,
         },
       },
     },
     menuBadge: {
       pointerEvents: "none",
       position: "absolute",
       right: space.s1,
       display: "flex",
       height: space.s5,
       minWidth: space.s5,
       alignItems: "center",
       justifyContent: "center",
       userSelect: "none",
       borderRadius: radius.md,
       paddingInline: space.s1,
       fontSize: text.xs,
       lineHeight: leading.xs,
       fontWeight: 500,
       fontVariantNumeric: "tabular-nums",
       color: {
         default: sidebarForeground,
         [stylex.when.ancestor(":hover", sidebarMenuItem)]: sidebarAccentForeground,
       },
     },
     menuSkeleton: {
       display: "flex",
       height: space.s8,
       alignItems: "center",
       gap: space.s2,
       borderRadius: radius.md,
       paddingInline: space.s2,
     },
     menuSkeletonIcon: { width: space.s4, height: space.s4, borderRadius: radius.md },
     menuSkeletonText: { height: space.s4, flexGrow: 1 },
     menuSub: {
       marginInline: space.s3_5,
       display: "flex",
       minWidth: 0,
       transform: "translateX(1px)",
       flexDirection: "column",
       gap: space.s1,
       borderLeftWidth: 1,
       borderLeftStyle: "solid",
       borderLeftColor: sidebarBorder,
       paddingInline: space.s2_5,
       paddingBlock: space.s0_5,
     },
     menuSubItem: { position: "relative" },
     menuSubButton: {
       display: "flex",
       height: space.s7,
       minWidth: 0,
       transform: "translateX(-1px)",
       alignItems: "center",
       gap: space.s2,
       overflow: "hidden",
       borderRadius: radius.md,
       paddingInline: space.s2,
       color: {
         default: sidebarForeground,
         ":hover": sidebarAccentForeground,
         ":active": sidebarAccentForeground,
       },
       backgroundColor: {
         default: null,
         ":hover": color.itemHighlightBg,
         ":active": color.itemHighlightBg,
       },
       transitionProperty: "color, background-color, box-shadow",
       transitionDuration: duration.color,
       transitionTimingFunction: ease.standard,
       pointerEvents: { default: null, ":disabled": "none" },
       opacity: { default: 1, ":disabled": 0.5 },
       "--carbon-icon-size": space.s4,
     },
     truncate: { overflow: "hidden", textOverflow: "ellipsis", whiteSpace: "nowrap" },
     hidden: { display: "none" },
   })

   const menuButtonVariants = stylex.create({
     default: {},
     outline: {
       backgroundColor: {
         default: color.background,
         ":hover": color.itemHighlightBg,
         ":active": color.itemHighlightBg,
       },
       boxShadow: {
         default: `0 0 0 1px ${sidebarBorder}`,
         ":hover": `0 0 0 1px ${sidebarAccent}`,
         ":focus-visible": shadow.rowRing,
       },
     },
   })

   const menuButtonSizes = stylex.create({
     default: { height: space.s8, fontSize: text.sm, lineHeight: leading.sm },
     sm: { height: space.s7, fontSize: text.xs, lineHeight: leading.xs },
     lg: { height: space.s12, fontSize: text.sm, lineHeight: leading.sm },
   })

   const menuActionTop = stylex.create({
     default: { top: space.s1_5 },
     sm: { top: space.s1 },
     lg: { top: space.s2_5 },
   })

   const menuSubButtonSizes = stylex.create({
     sm: { fontSize: text.xs, lineHeight: leading.xs },
     md: { fontSize: text.sm, lineHeight: leading.sm },
   })

   const sidebarStyles = {
     menuButton: [styles.menuButton, states.rowFocusRing],
     menuButtonVariants,
     menuButtonSizes,
     menuSubButton: [styles.menuSubButton, states.rowFocusRing],
     truncate: styles.truncate,
   }

   type SidebarMenuButtonVariant = keyof typeof menuButtonVariants
   type SidebarMenuButtonSize = keyof typeof menuButtonSizes
   type SidebarMenuSubButtonSize = keyof typeof menuSubButtonSizes

   type Overrides = { xstyle?: StyleXStyles; className?: string }

   function subscribeToViewport(onChange: () => void) {
     const list = window.matchMedia(MOBILE_QUERY)
     list.addEventListener("change", onChange)
     return () => list.removeEventListener("change", onChange)
   }

   function useIsMobile() {
     return useSyncExternalStore(
       subscribeToViewport,
       () => window.matchMedia(MOBILE_QUERY).matches,
       () => false,
     )
   }

   function useSidebar() {
     const context = use(SidebarContext)
     if (!context) {
       throw new Error("useSidebar must be used within a SidebarProvider.")
     }
     return context
   }

   type SidebarProviderProps = useRender.ComponentProps<"div"> &
     Overrides & {
       defaultOpen?: boolean
       open?: boolean
       onOpenChange?: (open: boolean) => void
       variant?: SidebarVariant
     }

   function SidebarProvider({
     render,
     ref,
     style,
     defaultOpen = true,
     open: openProp,
     onOpenChange: setOpenProp,
     variant = "sidebar",
     xstyle,
     className,
     children,
     ...props
   }: SidebarProviderProps) {
     const isMobile = useIsMobile()
     const [openMobile, setOpenMobile] = useState(false)

     const [uncontrolledOpen, setUncontrolledOpen] = useState(defaultOpen)
     const open = openProp ?? uncontrolledOpen

     const [instant, setInstant] = useState(false)
     const restoreFrom = useRef({ controlled: openProp !== undefined, open: uncontrolledOpen })
     useLayoutEffect(() => {
       if (restoreFrom.current.controlled) return
       const saved = new RegExp(`(?:^|; )${SIDEBAR_COOKIE_NAME}=(true|false)`).exec(
         document.cookie,
       )?.[1]
       if (saved === undefined || (saved === "true") === restoreFrom.current.open) return
       setInstant(true)
       setUncontrolledOpen(saved === "true")
       let frame = requestAnimationFrame(() => {
         frame = requestAnimationFrame(() => setInstant(false))
       })
       return () => cancelAnimationFrame(frame)
     }, [])
     const setOpen = useCallback(
       (value: boolean | ((open: boolean) => boolean)) => {
         const next = typeof value === "function" ? value(open) : value
         if (setOpenProp) {
           setOpenProp(next)
         } else {
           setUncontrolledOpen(next)
         }
         document.cookie = `${SIDEBAR_COOKIE_NAME}=${next}; path=/; max-age=${SIDEBAR_COOKIE_MAX_AGE}`
       },
       [setOpenProp, open],
     )

     const [peeking, setPeekingNow] = useState(false)
     const peekTimer = useRef<number | undefined>(undefined)
     const setPeeking = useCallback((next: boolean) => {
       window.clearTimeout(peekTimer.current)
       if (next) setPeekingNow(true)
       else peekTimer.current = window.setTimeout(() => setPeekingNow(false), SIDEBAR_PEEK_CLOSE_DELAY)
     }, [])
     useEffect(() => {
       if (open) {
         window.clearTimeout(peekTimer.current)
         setPeekingNow(false)
       }
     }, [open])
     useEffect(() => () => window.clearTimeout(peekTimer.current), [])

     const toggleSidebar = useCallback(() => {
       if (isMobile) {
         setOpenMobile((current) => !current)
       } else {
         setOpen((current) => !current)
       }
     }, [isMobile, setOpen])

     useEffect(() => {
       const onKeyDown = (event: KeyboardEvent) => {
         if (event.key === SIDEBAR_KEYBOARD_SHORTCUT && (event.metaKey || event.ctrlKey)) {
           event.preventDefault()
           toggleSidebar()
         } else if (event.key === "Escape") {
           window.clearTimeout(peekTimer.current)
           setPeekingNow(false)
         }
       }
       window.addEventListener("keydown", onKeyDown)
       return () => window.removeEventListener("keydown", onKeyDown)
     }, [toggleSidebar])

     const state: SidebarState = open ? "expanded" : "collapsed"

     const contextValue = useMemo<SidebarContextValue>(
       () => ({
         state,
         open,
         setOpen,
         isMobile,
         openMobile,
         setOpenMobile,
         toggleSidebar,
         variant,
         peeking,
         setPeeking,
         instant,
       }),
       [
         state,
         open,
         setOpen,
         isMobile,
         openMobile,
         toggleSidebar,
         variant,
         peeking,
         setPeeking,
         instant,
       ],
     )

     const sx = stylex.props(styles.wrapper, variant === "inset" && styles.wrapperInset, xstyle)

     return (
       <SidebarContext value={contextValue}>
         {useRender({
           defaultTagName: "div",
           render,
           ref,
           props: {
             "data-slot": "sidebar-wrapper",
             "data-variant": variant,
             ...sx,
             ...props,
             className: clsx(sx.className, className),
             style: { ...sx.style, ...style },
             children,
           },
         })}
       </SidebarContext>
     )
   }

   type SidebarProps = useRender.ComponentProps<"div"> &
     Overrides & {
       side?: SidebarSide
       collapsible?: SidebarCollapsible
     }

   function Sidebar({
     style,
     side = "left",
     collapsible = "offcanvas",
     xstyle,
     className,
     children,
     onPointerEnter,
     onPointerLeave,
     ...props
   }: SidebarProps) {
     const { isMobile, state, openMobile, setOpenMobile, variant, peeking, setPeeking, instant } =
       useSidebar()

     if (collapsible === "none") {
       const sx = stylex.props(styles.none, xstyle)
       return (
         <SidebarPanelContext value={{ side, collapsible, iconMode: false }}>
           <SidebarPanel
             data-slot="sidebar"
             data-side={side}
             data-variant={variant}
             {...sx}
             {...props}
             className={clsx(sx.className, className)}
             style={{ ...sx.style, ...style }}
           >
             {children}
           </SidebarPanel>
         </SidebarPanelContext>
       )
     }

     if (isMobile) {
       return (
         <SidebarPanelContext value={{ side, collapsible, iconMode: false }}>
           <Sheet open={openMobile} onOpenChange={setOpenMobile}>
             <SheetContent
               data-sidebar="sidebar"
               data-mobile=""
               data-side={side}
               side={side}
               showCloseButton={false}
               xstyle={[styles.sheet, xstyle]}
               style={style}
               className={className}
               {...props}
             >
               <SheetHeader xstyle={states.srOnly}>
                 <SheetTitle>Sidebar</SheetTitle>
                 <SheetDescription>Displays the mobile sidebar.</SheetDescription>
               </SheetHeader>
               <div {...stylex.props(styles.sheetBody)}>{children}</div>
             </SheetContent>
           </Sheet>
         </SidebarPanelContext>
       )
     }

     const collapsed = state === "collapsed"
     const offcanvas = collapsed && collapsible === "offcanvas"
     const peek = offcanvas && peeking
     const icon = collapsed && collapsible === "icon"
     const padded = variant === "floating" || variant === "inset"
     const sx = stylex.props(
       styles.container,
       side === "left" ? styles.containerLeft : styles.containerRight,
       offcanvas &&
         !peek &&
         (side === "left" ? styles.containerLeftOffcanvas : styles.containerRightOffcanvas),
       peek && (side === "left" ? styles.containerPeekLeft : styles.containerPeekRight),
       instant && styles.instant,
       padded && styles.containerPadded,
       icon && (padded ? styles.containerIconPadded : styles.containerIcon),
       !padded && (side === "left" ? styles.containerBorderRight : styles.containerBorderLeft),
       xstyle,
     )

     return (
       <SidebarPanelContext value={{ side, collapsible, iconMode: icon }}>
         <div
           data-slot="sidebar"
           data-state={state}
           data-collapsible={collapsed ? collapsible : ""}
           data-peek={peek ? "" : undefined}
           data-variant={variant}
           data-side={side}
           {...stylex.props(styles.root)}
         >
           <div
             data-slot="sidebar-gap"
             {...stylex.props(
               styles.gap,
               offcanvas && styles.gapOffcanvas,
               icon && (padded ? styles.gapIconPadded : styles.gapIcon),
               instant && styles.instant,
             )}
           />
           <SidebarPanel
             data-slot="sidebar-container"
             data-side={side}
             {...sx}
             {...props}
             onPointerEnter={(event) => {
               onPointerEnter?.(event)
               if (offcanvas) setPeeking(true)
             }}
             onPointerLeave={(event) => {
               onPointerLeave?.(event)
               if (offcanvas) setPeeking(false)
             }}
             className={clsx(sx.className, className)}
             style={{ ...sx.style, ...style }}
           >
             <div
               data-sidebar="sidebar"
               data-slot="sidebar-inner"
               {...stylex.props(
                 styles.inner,
                 variant === "floating" && styles.innerFloating,
                 variant === "inset" && styles.innerInset,
               )}
             >
               {children}
             </div>
           </SidebarPanel>
         </div>
       </SidebarPanelContext>
     )
   }

   function SidebarPanel({ render, ref, ...props }: useRender.ComponentProps<"div">) {
     return useRender({ defaultTagName: "div", render, ref, props })
   }

   type SidebarTriggerProps = ComponentProps<typeof Button> & {
     peek?: boolean
   }

   function SidebarTrigger({
     onClick,
     onPointerEnter,
     onPointerLeave,
     peek = false,
     ...props
   }: SidebarTriggerProps) {
     const { toggleSidebar, open, isMobile, setPeeking } = useSidebar()
     const peeks = peek && !open && !isMobile

     return (
       <Button
         data-slot="sidebar-trigger"
         variant="ghost"
         size="icon-sm"
         aria-label="Toggle sidebar"
         onClick={(event) => {
           onClick?.(event)
           toggleSidebar()
         }}
         onPointerEnter={(event) => {
           onPointerEnter?.(event)
           if (peeks) setPeeking(true)
         }}
         onPointerLeave={(event) => {
           onPointerLeave?.(event)
           if (peeks) setPeeking(false)
         }}
         {...props}
       >
         <PanelLeft />
       </Button>
     )
   }

   function SidebarRail({
     render,
     ref,
     style,
     xstyle,
     className,
     ...props
   }: useRender.ComponentProps<"button"> & Overrides) {
     const { toggleSidebar, state } = useSidebar()
     const { side, collapsible } = use(SidebarPanelContext)
     const collapsed = state === "collapsed"
     const offcanvas = collapsed && collapsible === "offcanvas"
     const left = side === "left"
     const sx = stylex.props(
       styles.rail,
       left ? styles.railLeft : styles.railRight,
       collapsed && (left ? styles.railLeftCollapsed : styles.railRightCollapsed),
       offcanvas && styles.railOffcanvas,
       offcanvas && (left ? styles.railLeftOffcanvas : styles.railRightOffcanvas),
       xstyle,
     )

     return useRender({
       defaultTagName: "button",
       render,
       ref,
       props: {
         type: "button",
         "data-slot": "sidebar-rail",
         "aria-label": "Toggle sidebar",
         tabIndex: -1,
         onClick: toggleSidebar,
         title: "Toggle sidebar",
         ...sx,
         ...props,
         className: clsx(sx.className, className),
         style: { ...sx.style, ...style },
       },
     })
   }

   type SidebarInsetProps = useRender.ComponentProps<"main"> & Overrides

   function SidebarInset({ render, ref, style, xstyle, className, ...props }: SidebarInsetProps) {
     const { variant, state } = useSidebar()
     const inset = variant === "inset"
     const sx = stylex.props(
       styles.inset,
       inset && styles.insetVariant,
       inset && state === "collapsed" && styles.insetVariantCollapsed,
       xstyle,
     )

     return useRender({
       defaultTagName: "main",
       render,
       ref,
       props: {
         "data-slot": "sidebar-inset",
         ...sx,
         ...props,
         className: clsx(sx.className, className),
         style: { ...sx.style, ...style },
       },
     })
   }

   function SidebarInput({ xstyle, ...props }: ComponentProps<typeof Input>) {
     return <Input data-slot="sidebar-input" xstyle={[styles.input, xstyle]} {...props} />
   }

   function SidebarHeader({
     xstyle,
     className,
     ...props
   }: useRender.ComponentProps<"div"> & Overrides) {
     return (
       <VStack
         data-slot="sidebar-header"
         gap={2}
         padding={2}
         xstyle={xstyle}
         className={className}
         {...props}
       />
     )
   }

   function SidebarFooter({
     xstyle,
     className,
     ...props
   }: useRender.ComponentProps<"div"> & Overrides) {
     return (
       <VStack
         data-slot="sidebar-footer"
         gap={2}
         padding={2}
         xstyle={xstyle}
         className={className}
         {...props}
       />
     )
   }

   function SidebarSeparator({ xstyle, ...props }: ComponentProps<typeof Separator>) {
     return <Separator data-slot="sidebar-separator" xstyle={[styles.separator, xstyle]} {...props} />
   }

   function SidebarContent({
     xstyle,
     className,
     ...props
   }: useRender.ComponentProps<"div"> & Overrides) {
     const { iconMode } = use(SidebarPanelContext)
     return (
       <VStack
         data-slot="sidebar-content"
         grow
         scrollable
         xstyle={[iconMode && styles.contentIcon, xstyle]}
         className={clsx("no-scrollbar", className)}
         {...props}
       />
     )
   }

   function SidebarGroup({
     xstyle,
     className,
     ...props
   }: useRender.ComponentProps<"div"> & Overrides) {
     return (
       <VStack
         data-slot="sidebar-group"
         padding={2}
         xstyle={[styles.group, xstyle]}
         className={className}
         {...props}
       />
     )
   }

   type SidebarGroupLabelProps = useRender.ComponentProps<"div"> & { xstyle?: StyleXStyles }

   function SidebarGroupLabel({ xstyle, className, render, ...props }: SidebarGroupLabelProps) {
     const { iconMode } = use(SidebarPanelContext)
     const sx = stylex.props(
       styles.groupLabel,
       states.rowFocusRing,
       iconMode && styles.groupLabelIcon,
       xstyle,
     )

     return useRender({
       defaultTagName: "div",
       render,
       props: {
         "data-slot": "sidebar-group-label",
         ...sx,
         ...props,
         className: clsx("carbon-icons", sx.className, className),
       },
     })
   }

   type SidebarGroupActionProps = useRender.ComponentProps<"button"> & { xstyle?: StyleXStyles }

   function SidebarGroupAction({ xstyle, className, render, ...props }: SidebarGroupActionProps) {
     const { iconMode } = use(SidebarPanelContext)
     const sx = stylex.props(
       styles.groupAction,
       states.rowFocusRing,
       iconMode && styles.hidden,
       xstyle,
     )

     return useRender({
       defaultTagName: "button",
       render,
       props: {
         type: "button",
         "data-slot": "sidebar-group-action",
         ...sx,
         ...props,
         className: clsx("carbon-icons", sx.className, className),
       },
     })
   }

   function SidebarGroupContent({
     render,
     ref,
     style,
     xstyle,
     className,
     ...props
   }: useRender.ComponentProps<"div"> & Overrides) {
     const sx = stylex.props(styles.groupContent, xstyle)
     return useRender({
       defaultTagName: "div",
       render,
       ref,
       props: {
         "data-slot": "sidebar-group-content",
         ...sx,
         ...props,
         className: clsx(sx.className, className),
         style: { ...sx.style, ...style },
       },
     })
   }

   function SidebarMenu({ xstyle, className, ...props }: useRender.ComponentProps<"ul"> & Overrides) {
     return (
       <VStack<"ul">
         render={<ul />}
         data-slot="sidebar-menu"
         gap={1}
         xstyle={[styles.menu, xstyle]}
         className={className}
         {...props}
       />
     )
   }

   function SidebarMenuItem({
     render,
     ref,
     style,
     xstyle,
     className,
     ...props
   }: useRender.ComponentProps<"li"> & Overrides) {
     const [size, setSize] = useState<SidebarMenuButtonSize>("default")
     const [hasAction, setHasAction] = useState(false)
     const value = useMemo(() => ({ size, setSize, hasAction, setHasAction }), [size, hasAction])
     const sx = stylex.props(styles.menuItem, sidebarMenuItem, xstyle)

     return (
       <SidebarMenuItemContext value={value}>
         {useRender({
           defaultTagName: "li",
           render,
           ref,
           props: {
             "data-slot": "sidebar-menu-item",
             ...sx,
             ...props,
             className: clsx(sx.className, className),
             style: { ...sx.style, ...style },
           },
         })}
       </SidebarMenuItemContext>
     )
   }

   type SidebarMenuButtonProps = useRender.ComponentProps<"button", { active: boolean }> & {
     isActive?: boolean
     variant?: SidebarMenuButtonVariant
     size?: SidebarMenuButtonSize
     tooltip?: string | ComponentProps<typeof TooltipContent>
     xstyle?: StyleXStyles
   }

   function SidebarMenuButton({
     render,
     isActive = false,
     variant = "default",
     size = "default",
     tooltip,
     xstyle,
     className,
     ...props
   }: SidebarMenuButtonProps) {
     const { isMobile, state } = useSidebar()
     const { iconMode } = use(SidebarPanelContext)
     const item = use(SidebarMenuItemContext)

     useLayoutEffect(() => {
       item?.setSize(size)
     }, [item, size])

     const sx = stylex.props(
       styles.menuButton,
       states.rowFocusRing,
       menuButtonVariants[variant] ?? menuButtonVariants.default,
       menuButtonSizes[size] ?? menuButtonSizes.default,
       isActive && styles.rowActive,
       isActive && styles.rowActiveLabel,
       item?.hasAction && !iconMode && styles.menuButtonWithAction,
       iconMode && styles.menuButtonIcon,
       iconMode && size === "lg" && styles.menuButtonIconLg,
       xstyle,
     )

     const button = useRender({
       defaultTagName: "button",
       render: tooltip ? (
         <TooltipTrigger
           render={
             typeof render === "function" ? (props) => render(props, { active: isActive }) : render
           }
         />
       ) : (
         render
       ),
       state: { active: isActive },
       stateAttributesMapping: { active: (active) => (active ? { "data-active": "true" } : null) },
       props: {
         type: render ? undefined : "button",
         "data-slot": "sidebar-menu-button",
         "data-sidebar": "menu-button",
         "data-variant": variant,
         "data-size": size,
         ...sx,
         ...props,
         className: clsx("carbon-icons", sx.className, className),
       },
     })

     if (!tooltip) return button

     const content = typeof tooltip === "string" ? { children: tooltip } : tooltip

     return (
       <Tooltip>
         {button}
         {state === "collapsed" && !isMobile && (
           <TooltipContent side="right" align="center" {...content} />
         )}
       </Tooltip>
     )
   }

   type SidebarMenuActionProps = useRender.ComponentProps<"button"> & {
     showOnHover?: boolean
     xstyle?: StyleXStyles
   }

   function SidebarMenuAction({
     xstyle,
     className,
     render,
     showOnHover = false,
     ...props
   }: SidebarMenuActionProps) {
     const { iconMode } = use(SidebarPanelContext)
     const item = use(SidebarMenuItemContext)

     useLayoutEffect(() => {
       if (!item) return
       item.setHasAction(true)
       return () => item.setHasAction(false)
     }, [item])

     const sx = stylex.props(
       styles.menuAction,
       states.rowFocusRing,
       menuActionTop[item?.size ?? "default"],
       showOnHover && styles.menuActionShowOnHover,
       iconMode && styles.hidden,
       xstyle,
     )

     return useRender({
       defaultTagName: "button",
       render,
       props: {
         type: render ? undefined : "button",
         "data-slot": "sidebar-menu-action",
         "data-sidebar": "menu-action",
         ...sx,
         ...props,
         className: clsx("carbon-icons", sx.className, className),
       },
     })
   }

   function SidebarMenuBadge({
     render,
     ref,
     style,
     xstyle,
     className,
     ...props
   }: useRender.ComponentProps<"div"> & Overrides) {
     const { iconMode } = use(SidebarPanelContext)
     const item = use(SidebarMenuItemContext)
     const sx = stylex.props(
       styles.menuBadge,
       menuActionTop[item?.size ?? "default"],
       iconMode && styles.hidden,
       xstyle,
     )

     return useRender({
       defaultTagName: "div",
       render,
       ref,
       props: {
         "data-slot": "sidebar-menu-badge",
         ...sx,
         ...props,
         className: clsx(sx.className, className),
         style: { ...sx.style, ...style },
       },
     })
   }

   type SidebarMenuSkeletonProps = useRender.ComponentProps<"div"> & Overrides & { showIcon?: boolean }

   function SidebarMenuSkeleton({
     render,
     ref,
     style,
     xstyle,
     className,
     showIcon = false,
     ...props
   }: SidebarMenuSkeletonProps) {
     const id = useId()
     let hash = 0
     for (const char of id) hash = (hash * 31 + char.charCodeAt(0)) % 41
     const width = `${50 + hash}%`
     const sx = stylex.props(styles.menuSkeleton, xstyle)

     return useRender({
       defaultTagName: "div",
       render,
       ref,
       props: {
         "data-slot": "sidebar-menu-skeleton",
         ...sx,
         ...props,
         className: clsx(sx.className, className),
         style: { ...sx.style, ...style },
         children: (
           <>
             {showIcon && (
               <Skeleton data-slot="sidebar-menu-skeleton-icon" xstyle={styles.menuSkeletonIcon} />
             )}
             <Skeleton
               data-slot="sidebar-menu-skeleton-text"
               xstyle={styles.menuSkeletonText}
               style={{ maxWidth: width }}
             />
           </>
         ),
       },
     })
   }

   function SidebarMenuSub({
     render,
     ref,
     style,
     xstyle,
     className,
     ...props
   }: useRender.ComponentProps<"ul"> & Overrides) {
     const { iconMode } = use(SidebarPanelContext)
     const sx = stylex.props(styles.menuSub, iconMode && styles.hidden, xstyle)

     return useRender({
       defaultTagName: "ul",
       render,
       ref,
       props: {
         "data-slot": "sidebar-menu-sub",
         ...sx,
         ...props,
         className: clsx(sx.className, className),
         style: { ...sx.style, ...style },
       },
     })
   }

   function SidebarMenuSubItem({
     render,
     ref,
     style,
     xstyle,
     className,
     ...props
   }: useRender.ComponentProps<"li"> & Overrides) {
     const sx = stylex.props(styles.menuSubItem, xstyle)
     return useRender({
       defaultTagName: "li",
       render,
       ref,
       props: {
         "data-slot": "sidebar-menu-sub-item",
         ...sx,
         ...props,
         className: clsx(sx.className, className),
         style: { ...sx.style, ...style },
       },
     })
   }

   type SidebarMenuSubButtonProps = useRender.ComponentProps<"a", { active: boolean }> & {
     size?: SidebarMenuSubButtonSize
     isActive?: boolean
     xstyle?: StyleXStyles
   }

   function SidebarMenuSubButton({
     render,
     size = "md",
     isActive = false,
     xstyle,
     className,
     ...props
   }: SidebarMenuSubButtonProps) {
     const { iconMode } = use(SidebarPanelContext)
     const sx = stylex.props(
       styles.menuSubButton,
       states.rowFocusRing,
       menuSubButtonSizes[size] ?? menuSubButtonSizes.md,
       isActive && styles.rowActive,
       iconMode && styles.hidden,
       xstyle,
     )

     return useRender({
       defaultTagName: "a",
       render,
       state: { active: isActive },
       stateAttributesMapping: { active: (active) => (active ? { "data-active": "true" } : null) },
       props: {
         "data-slot": "sidebar-menu-sub-button",
         "data-size": size,
         ...sx,
         ...props,
         className: clsx("carbon-icons", sx.className, className),
       },
     })
   }

   export {
     Sidebar,
     SidebarContent,
     SidebarFooter,
     SidebarGroup,
     SidebarGroupAction,
     SidebarGroupContent,
     SidebarGroupLabel,
     SidebarHeader,
     SidebarInput,
     SidebarInset,
     SidebarMenu,
     SidebarMenuAction,
     SidebarMenuBadge,
     SidebarMenuButton,
     SidebarMenuItem,
     SidebarMenuSkeleton,
     SidebarMenuSub,
     SidebarMenuSubButton,
     SidebarMenuSubItem,
     SidebarProvider,
     SidebarRail,
     SidebarSeparator,
     SidebarTrigger,
     sidebarStyles,
     useSidebar,
     type SidebarCollapsible,
     type SidebarMenuButtonSize,
     type SidebarMenuButtonVariant,
     type SidebarMenuSubButtonSize,
     type SidebarSide,
     type SidebarVariant,
   }
   ```

4. Update the import paths to match your project setup.

## Usage

```tsx title="app/layout.tsx"
import { SidebarProvider, SidebarTrigger } from "@/components/ui/sidebar"
import { AppSidebar } from "@/components/app-sidebar"

export default function Layout({ children }: { children: React.ReactNode }) {
  return (
    <SidebarProvider>
      <AppSidebar />
      <main>
        <SidebarTrigger />
        {children}
      </main>
    </SidebarProvider>
  )
}
```

```tsx title="components/app-sidebar.tsx"
import {
  Sidebar,
  SidebarContent,
  SidebarFooter,
  SidebarGroup,
  SidebarHeader,
} from "@/components/ui/sidebar"

export function AppSidebar() {
  return (
    <Sidebar>
      <SidebarHeader />
      <SidebarContent>
        <SidebarGroup />
        <SidebarGroup />
      </SidebarContent>
      <SidebarFooter />
    </Sidebar>
  )
}
```

## Composition

```text
SidebarProvider
├── Sidebar
│   ├── SidebarHeader
│   ├── SidebarContent
│   │   ├── SidebarGroup
│   │   │   ├── SidebarGroupLabel
│   │   │   ├── SidebarGroupAction
│   │   │   ├── SidebarGroupContent
│   │   │   └── SidebarMenu
│   │   │       ├── SidebarMenuItem
│   │   │       │   ├── SidebarMenuButton
│   │   │       │   ├── SidebarMenuAction
│   │   │       │   └── SidebarMenuBadge
│   │   │       └── SidebarMenuItem
│   │   │           ├── SidebarMenuButton
│   │   │           └── SidebarMenuSub
│   │   │               ├── SidebarMenuSubItem
│   │   │               └── SidebarMenuSubItem
│   │   └── SidebarGroup
│   │       └── SidebarMenu
│   │           ├── SidebarMenuItem
│   │           └── SidebarMenuItem
│   ├── SidebarFooter
│   └── SidebarRail
├── SidebarInset
└── SidebarTrigger
```

Above `48rem` the `Sidebar` is a fixed column beside an in-flow spacer that
animates its width to push the page over. Below it, the same children render inside a
[Sheet](https://carbon.style/docs/components/sheet) on the same side.

The `variant` prop is on `SidebarProvider`, not on `Sidebar` as in shadcn. The
`inset` variant styles the wrapper and the `SidebarInset` beside the sidebar as
well, and neither can see a sibling's prop.

There is no RTL section. carbon does not support RTL yet, so the offcanvas
offset and the rail use LTR physical properties.

## Structure

- **SidebarProvider** handles collapsible state and provides sidebar context to
  child components.
- **Sidebar** is the main collapsible sidebar panel.
- **SidebarHeader** is sticky at the top. Use it for branding, titles, or
  workspace switchers.
- **SidebarFooter** is sticky at the bottom. Use it for user menus, settings,
  or actions.
- **SidebarContent** is the scrollable region between the header and footer.
- **SidebarGroup** groups related navigation with optional label, action, and
  content areas.
- **SidebarMenu** and **SidebarMenuItem** hold links, badges, actions, and
  nested submenus.
- **SidebarRail** is a strip along the sidebar's edge that toggles it.
- **SidebarInset** wraps the main content when using the `inset` variant.
- **SidebarTrigger** toggles the sidebar open or collapsed.

## SidebarProvider

Wrap the layout in a `SidebarProvider`. It holds the open state for the desktop
panel and the mobile sheet, persists the desktop one in a `carbon-sidebar`
cookie, and binds ⌘B on Mac and Ctrl+B on Windows.

| Prop           | Type                                 | Default     |
| -------------- | ------------------------------------ | ----------- |
| `defaultOpen`  | `boolean`                            | `true`      |
| `open`         | `boolean`                            | —           |
| `onOpenChange` | `(open: boolean) => void`            | —           |
| `variant`      | `"sidebar" \| "floating" \| "inset"` | `"sidebar"` |

### Width

The panel is `16rem` wide, `3rem` as an icon rail and `18rem` as a sheet. The
first two are custom properties on the wrapper. Set them with `style` on the
provider; they are plain custom properties, so every part inside reads them.

```tsx
const wide = { "--carbon-sidebar-width": "20rem", "--carbon-sidebar-width-icon": "4rem" }

<SidebarProvider style={wide as CSSProperties}>
  <Sidebar />
</SidebarProvider>
```

### Keyboard shortcut

`SIDEBAR_KEYBOARD_SHORTCUT` in `sidebar.tsx` is the key that toggles the sidebar
with ⌘ or Ctrl held.

```tsx title="components/ui/sidebar.tsx"
const SIDEBAR_KEYBOARD_SHORTCUT = "b"
```

## Sidebar

`Sidebar` renders the collapsible panel.

| Prop          | Type                              | Default       |
| ------------- | --------------------------------- | ------------- |
| `side`        | `"left" \| "right"`               | `"left"`      |
| `collapsible` | `"offcanvas" \| "icon" \| "none"` | `"offcanvas"` |

| `collapsible` | Description                                                  |
| ------------- | ------------------------------------------------------------ |
| `offcanvas`   | A collapsible sidebar that slides in from the left or right. |
| `icon`        | A sidebar that collapses to icons.                           |
| `none`        | A non-collapsible sidebar.                                   |

> **Note**
>
> With `variant="inset"` on the provider, wrap the main content in a
> `SidebarInset`. It draws the panel the page is raised in, out of the tray the
> sidebar sits on, and moves over when the sidebar collapses.

```tsx
<SidebarProvider variant="inset">
  <Sidebar />
  <SidebarInset>
    <main>{children}</main>
  </SidebarInset>
</SidebarProvider>
```

## useSidebar

Use the `useSidebar` hook to control the sidebar.

```tsx
import { useSidebar } from "@/components/ui/sidebar"

export function AppSidebar() {
  const { state, open, setOpen, openMobile, setOpenMobile, isMobile, toggleSidebar } = useSidebar()
}
```

| Property        | Type                        | Description                                   |
| --------------- | --------------------------- | --------------------------------------------- |
| `state`         | `"expanded" \| "collapsed"` | The current state of the sidebar.             |
| `open`          | `boolean`                   | Whether the sidebar is open.                  |
| `setOpen`       | `(open: boolean) => void`   | Sets the open state of the sidebar.           |
| `openMobile`    | `boolean`                   | Whether the sidebar is open on mobile.        |
| `setOpenMobile` | `(open: boolean) => void`   | Sets the open state of the sidebar on mobile. |
| `isMobile`      | `boolean`                   | Whether the sidebar is on mobile.             |
| `toggleSidebar` | `() => void`                | Toggles the sidebar on desktop and mobile.    |

## SidebarHeader

Use the `SidebarHeader` component to add a sticky header to the sidebar.

```tsx title="components/app-sidebar.tsx"
<Sidebar>
  <SidebarHeader>
    <SidebarMenu>
      <SidebarMenuItem>
        <DropdownMenu>
          <DropdownMenuTrigger render={<SidebarMenuButton />}>
            Select Workspace
            <ChevronDown {...stylex.props(styles.trailing)} />
          </DropdownMenuTrigger>
          <DropdownMenuContent>
            <DropdownMenuItem>Acme Inc</DropdownMenuItem>
          </DropdownMenuContent>
        </DropdownMenu>
      </SidebarMenuItem>
    </SidebarMenu>
  </SidebarHeader>
</Sidebar>
```

A menu button that is an open menu's trigger keeps its hover fill while the
menu is open. The `aria-expanded` rule in `interop.css` does this.

## SidebarFooter

Use the `SidebarFooter` component to add a sticky footer to the sidebar.

```tsx
<Sidebar>
  <SidebarFooter>
    <SidebarMenu>
      <SidebarMenuItem>
        <SidebarMenuButton>
          <User2 /> Username
        </SidebarMenuButton>
      </SidebarMenuItem>
    </SidebarMenu>
  </SidebarFooter>
</Sidebar>
```

## SidebarContent

`SidebarContent` wraps the content of the sidebar and scrolls. Put your
`SidebarGroup` components in it.

```tsx
<Sidebar>
  <SidebarContent>
    <SidebarGroup />
    <SidebarGroup />
  </SidebarContent>
</Sidebar>
```

## SidebarGroup

Use the `SidebarGroup` component to create a section within the sidebar.

A `SidebarGroup` has a `SidebarGroupLabel`, a `SidebarGroupContent` and an
optional `SidebarGroupAction`. Give the action an `aria-label`. It is an
icon-only button, so there is no text for a screen reader to announce.

```tsx
<SidebarGroup>
  <SidebarGroupLabel>Application</SidebarGroupLabel>
  <SidebarGroupAction aria-label="Add project">
    <Plus />
  </SidebarGroupAction>
  <SidebarGroupContent></SidebarGroupContent>
</SidebarGroup>
```

To make a `SidebarGroup` collapsible, wrap it in a `Collapsible` and render the
label as the trigger. `collapsibleStyles.chevron` turns the chevron with the
panel.

```tsx
<Collapsible defaultOpen>
  <SidebarGroup>
    <SidebarGroupLabel render={<CollapsibleTrigger />}>
      Help
      <ChevronDown {...stylex.props(collapsibleStyles.chevron, styles.trailing)} />
    </SidebarGroupLabel>
    <CollapsibleContent>
      <SidebarGroupContent />
    </CollapsibleContent>
  </SidebarGroup>
</Collapsible>
```

## SidebarMenu

Use `SidebarMenu` to build a menu within a `SidebarGroup`.

```tsx
<SidebarMenu>
  {projects.map((project) => (
    <SidebarMenuItem key={project.name}>
      <SidebarMenuButton render={<a href={project.url} />}>
        <project.icon />
        <span {...stylex.props(sidebarStyles.truncate)}>{project.name}</span>
      </SidebarMenuButton>
    </SidebarMenuItem>
  ))}
</SidebarMenu>
```

## SidebarMenuButton

`SidebarMenuButton` renders a menu button within a `SidebarMenuItem`.

It renders a button by default. Use the `render` prop to render a different
component, such as a `Link` or an `a` tag.

Use the `isActive` prop to mark a menu item as active.

```tsx
<SidebarMenuButton render={<a href="#" />} isActive>
  Home
</SidebarMenuButton>
```

Put a label that can outgrow the row in a `span` with `sidebarStyles.truncate`.
StyleX has no `[&>span:last-child]`, so the row cannot select the label. The
label takes the truncation style itself.

Pass `tooltip` to label the row while the sidebar is collapsed to icons. A string
is the label; an object is `TooltipContent`'s props. The tooltip renders only in
that state.

```tsx
<SidebarMenuButton tooltip="Playground">
  <SquareTerminal />
  <span>Playground</span>
</SidebarMenuButton>
```

## SidebarMenuAction

`SidebarMenuAction` renders a menu action within a `SidebarMenuItem`. Give it
an `aria-label`.

```tsx
<SidebarMenuItem>
  <SidebarMenuButton render={<a href="#" />}>
    <Home />
    <span>Home</span>
  </SidebarMenuButton>
  <SidebarMenuAction aria-label="Add project">
    <Plus />
  </SidebarMenuAction>
</SidebarMenuItem>
```

Pass `showOnHover` to hide the action until the row is hovered or holds focus.
Below `48rem` it always shows; there is no pointer to hover with.

> **Note**
>
> The action registers with its `SidebarMenuItem` after mount, and the button
> then pads its end so a long label truncates before the icon. Prerendered HTML
> ships without that padding, so a label that reaches the action moves by `2rem`
> on hydration.

## SidebarMenuSub

`SidebarMenuSub` renders a submenu within a `SidebarMenu`.

```tsx
<SidebarMenuItem>
  <SidebarMenuButton />
  <SidebarMenuSub>
    <SidebarMenuSubItem>
      <SidebarMenuSubButton />
    </SidebarMenuSubItem>
  </SidebarMenuSub>
</SidebarMenuItem>
```

## SidebarMenuBadge

`SidebarMenuBadge` renders a badge within a `SidebarMenuItem`.

```tsx
<SidebarMenuItem>
  <SidebarMenuButton />
  <SidebarMenuBadge>24</SidebarMenuBadge>
</SidebarMenuItem>
```

## SidebarMenuSkeleton

`SidebarMenuSkeleton` renders a skeleton for a `SidebarMenu`. The text bar is between 50% and 90% wide, chosen from the
element's id so the server and the client agree.

```tsx
<SidebarMenu>
  {Array.from({ length: 5 }).map((_, index) => (
    <SidebarMenuItem key={index}>
      <SidebarMenuSkeleton />
    </SidebarMenuItem>
  ))}
</SidebarMenu>
```

## SidebarTrigger

Use the `SidebarTrigger` component to render a button that toggles the sidebar.
It is a ghost `icon-sm` [Button](https://carbon.style/docs/components/button) and takes every prop
one does.

```tsx
import { useSidebar } from "@/components/ui/sidebar"

export function CustomTrigger() {
  const { toggleSidebar } = useSidebar()

  return <button onClick={toggleSidebar}>Toggle Sidebar</button>
}
```

## SidebarRail

`SidebarRail` renders a rail within a `Sidebar` that toggles the sidebar. It is
out of the tab order, so keyboard users toggle the sidebar with the trigger.

```tsx
<Sidebar>
  <SidebarHeader />
  <SidebarContent>
    <SidebarGroup />
  </SidebarContent>
  <SidebarFooter />
  <SidebarRail />
</Sidebar>
```

## Controlled sidebar

Use the `open` and `onOpenChange` props to control the sidebar.

```tsx
export function AppSidebar() {
  const [open, setOpen] = useState(false)

  return (
    <SidebarProvider open={open} onOpenChange={setOpen}>
      <Sidebar />
    </SidebarProvider>
  )
}
```

## Theming

The sidebar reads its own tokens, so it can differ from the page around it.
They are in `tokens.stylex.ts` beside the rest.

```ts title="theme/tokens.stylex.ts"
sidebar: "light-dark(oklch(0.985 0 0), oklch(0.205 0 0))",
sidebarForeground: "light-dark(oklch(0.145 0 0), oklch(0.985 0 0))",
sidebarPrimary: "light-dark(oklch(0.205 0 0), oklch(0.922 0 0))",
sidebarPrimaryForeground: "light-dark(oklch(0.985 0 0), oklch(0.205 0 0))",
sidebarAccent: "light-dark(oklch(0.95 0 0), oklch(0.269 0 0))",
sidebarAccentForeground: "light-dark(oklch(0.205 0 0), oklch(0.985 0 0))",
sidebarBorder: "light-dark(oklch(0.922 0 0), oklch(1 0 0 / 10%))",
```

## Styling

Every part takes `xstyle`. A part that changes with the sidebar's state reads
that state from context, so there is no `group-data-[collapsible=icon]` to key
on. Read `useSidebar` and pass a conditional style instead.

```tsx
const { state } = useSidebar()

<SidebarGroup xstyle={[state === "collapsed" && styles.hidden]} />
```

## API reference

Pass `render` to replace a part's element or compose it with another component.

Every part renders a plain element, or Base UI's `useRender` where it takes
`render`. Each part also takes `xstyle` and `className`.

### SidebarProvider

| Prop           | Type                                 | Default     |
| -------------- | ------------------------------------ | ----------- |
| `defaultOpen`  | `boolean`                            | `true`      |
| `open`         | `boolean`                            | —           |
| `onOpenChange` | `(open: boolean) => void`            | —           |
| `variant`      | `"sidebar" \| "floating" \| "inset"` | `"sidebar"` |

### Sidebar

| Prop          | Type                              | Default       |
| ------------- | --------------------------------- | ------------- |
| `side`        | `"left" \| "right"`               | `"left"`      |
| `collapsible` | `"offcanvas" \| "icon" \| "none"` | `"offcanvas"` |

### SidebarMenuButton

| Prop       | Type                                             | Default     |
| ---------- | ------------------------------------------------ | ----------- |
| `render`   | `ReactElement \| (props, state) => ReactElement` | —           |
| `isActive` | `boolean`                                        | `false`     |
| `variant`  | `"default" \| "outline"`                         | `"default"` |
| `size`     | `"default" \| "sm" \| "lg"`                      | `"default"` |
| `tooltip`  | `string \| TooltipContentProps`                  | —           |

### SidebarMenuAction

| Prop          | Type                                             | Default |
| ------------- | ------------------------------------------------ | ------- |
| `render`      | `ReactElement \| (props, state) => ReactElement` | —       |
| `showOnHover` | `boolean`                                        | `false` |

### SidebarMenuSubButton

| Prop       | Type                                             | Default |
| ---------- | ------------------------------------------------ | ------- |
| `render`   | `ReactElement \| (props, state) => ReactElement` | —       |
| `isActive` | `boolean`                                        | `false` |
| `size`     | `"sm" \| "md"`                                   | `"md"`  |

### SidebarMenuSkeleton

| Prop       | Type      | Default |
| ---------- | --------- | ------- |
| `showIcon` | `boolean` | `false` |

### sidebarStyles

The style maps behind the menu buttons, exported so the same row can be put on
another element.

| Key                  | Description                                     |
| -------------------- | ----------------------------------------------- |
| `menuButton`         | The row: box, hover fill, focus ring, icon size |
| `menuButtonVariants` | One style per `variant`                         |
| `menuButtonSizes`    | One style per `size`, with the height and type  |
| `menuSubButton`      | The nested row, one step shorter                |
| `truncate`           | Ellipsis for the label `span` in a row          |

### Data attributes

| Attribute                                                    | On                      | Effect                                                        |
| ------------------------------------------------------------ | ----------------------- | ------------------------------------------------------------- |
| `data-slot="sidebar-wrapper"`                                | the provider's `div`    |                                                               |
| `data-variant`                                               | wrapper, sidebar        | Mirrors the provider's `variant`                              |
| `data-slot="sidebar"`                                        | the sidebar root        | Absent below `48rem`, where the sheet renders                 |
| `data-state`                                                 | the sidebar root        | `"expanded"` or `"collapsed"`                                 |
| `data-collapsible`                                           | the sidebar root        | The `collapsible` prop while collapsed, `""` while open       |
| `data-side`                                                  | root, container         | Mirrors `side`                                                |
| `data-slot="sidebar-gap"`                                    | the in-flow spacer      | Animates its width with the panel                             |
| `data-slot="sidebar-container"`                              | the fixed panel         | Where `xstyle` and `className` land                           |
| `data-slot="sidebar-inner"`                                  | the panel's surface     | Fill, and the floating variant's raised shadow                |
| `data-sidebar="sidebar"`, `data-mobile`                      | the sheet popup         | Written beside the sheet's own `data-slot`                    |
| `data-slot="sidebar-trigger"`                                | the trigger             |                                                               |
| `data-slot="sidebar-rail"`                                   | the rail                |                                                               |
| `data-slot="sidebar-inset"`                                  | the `main`              |                                                               |
| `data-slot="sidebar-header"`, `-footer`, `-content`          | each region             |                                                               |
| `data-slot="sidebar-group"`, `-label`, `-action`, `-content` | each group part         |                                                               |
| `data-slot="sidebar-menu"`, `-item`, `-sub`, `-sub-item`     | each list part          |                                                               |
| `data-slot="sidebar-menu-button"`                            | a row                   | Replaced by the trigger's slot when rendered through `render` |
| `data-sidebar="menu-button"`                                 | a row                   | Where `interop.css` keys the open-menu fill                   |
| `data-variant`, `data-size`, `data-active`                   | a row, a nested row     | Mirror the props; `data-active` is present only when true     |
| `data-slot="sidebar-menu-action"`                            | an action               | Replaced by the trigger's slot when rendered through `render` |
| `data-sidebar="menu-action"`                                 | an action               | Where `interop.css` keys the open-menu opacity                |
| `data-slot="sidebar-menu-badge"`                             | a badge                 |                                                               |
| `data-slot="sidebar-menu-skeleton"`, `-icon`, `-text`        | the skeleton parts      |                                                               |
| `aria-haspopup`, `aria-expanded`                             | a row that opens a menu | Base UI writes them                                           |
