Skip to content
Components

Sidebar

BetaBeta — the props contract is close to final, but small breaking changes can still land.

Responsive app sidebar with resizing, offcanvas and icon collapse, mobile navigation, and nested menus.

Examples

Mobile bottom navigation

Below lg, SidebarMobileNav shows four primary destinations and Menu. Define destinations, icons, availability, and active state once, then render the same items with SidebarMenuButton or SidebarMobileNavItem. Sidebar mobileVariant="drawer" reuses the complete navigation in a bottom drawer, including with collapsible="none". Keep at most five visible controls, counting Menu. Desktop navigation stays in the sidebar.

Floating mobile menu

Use SidebarMobileNav variant="floating" with SidebarMobileTrigger when a persistent destination bar is not useful. The labelled Menu button opens the same complete navigation. Both presentations share SidebarProvider, support safe areas, reserve content space, and stay inside a layout="contained" preview. Keep the navigation definition and route matching in your application; use render for your router links.

Resizable sidebar

Add SidebarRail with resizable to drag or use arrow keys to resize. Drag below minWidth to collapse; click or press Enter to toggle. SidebarProvider accepts defaultWidth or width/onWidthChange in pixels. Keep saved preferences in your application.

Right sidebar

Use side="right" for navigation on the other edge. The rail follows that edge for pointer and keyboard resizing. This example controls width through SidebarProvider.

Nested navigation

Compose Collapsible with SidebarMenuButton and SidebarMenuSub for project subpages. Group labels also render CollapsibleTrigger to fold entire categories. Nested menus hide when the desktop sidebar collapses to icons.

Floating sidebar

Use variant="floating" to give the navigation its own bordered surface. On mobile it opens as an edge sheet with the same navigation.

Inset workspace

Use variant="inset" with SidebarInset to frame the content inside the workspace. Each preview contains one complete app shell; use its trigger to collapse the sidebar or open it on mobile.

Composition

Shared mobile navigation

Declare destinations and their behavior once in the application. Render the same items for the desktop sidebar and mobile primary destinations; Menu opens the existing sidebar content. The provider shares visibility, while the application owns route selection.

  • <SidebarProvider>
    • <Sidebar>
      • mobileVariant="drawer"; complete navigation
        • <SidebarContent>
          • <SidebarMenu />
          </SidebarContent>
      </Sidebar>
    • <SidebarInset>
      • route content
      </SidebarInset>
    • <SidebarMobileNav>
      • <SidebarMobileNavItem />
      • <SidebarMobileTrigger />
      </SidebarMobileNav>
    </SidebarProvider>

Application shell

  • <SidebarProvider>
    • <Sidebar>
      • <SidebarHeader />
      • <SidebarContent>
        • <SidebarGroup>
          • <SidebarGroupLabel />
          • <SidebarGroupContent>
            • <SidebarMenu>
              • <SidebarMenuItem>
                • <SidebarMenuButton />
                • <SidebarMenuBadge />
                • <SidebarMenuAction />
                • <SidebarMenuSub>
                  • <SidebarMenuItem>
                    • <SidebarMenuButton />
                    </SidebarMenuItem>
                  </SidebarMenuSub>
                </SidebarMenuItem>
              </SidebarMenu>
            </SidebarGroupContent>
          </SidebarGroup>
        </SidebarContent>
      • <SidebarFooter />
      • <SidebarRail />
      </Sidebar>
    • <SidebarInset>
      • <SidebarTrigger />
      </SidebarInset>
    </SidebarProvider>

Collapsible navigation group

  • <SidebarProvider>
    • <Sidebar>
      • <SidebarContent>
        • <SidebarGroup>
          • <SidebarMenu>
            • <SidebarMenuItem>
              • <Collapsible>
                • <SidebarMenuButton>
                  • render prop
                    • <CollapsibleTrigger />
                  </SidebarMenuButton>
                • <CollapsibleContent>
                  • <SidebarMenuSub>
                    • <SidebarMenuItem>
                      • <SidebarMenuButton />
                      </SidebarMenuItem>
                    </SidebarMenuSub>
                  </CollapsibleContent>
                </Collapsible>
              </SidebarMenuItem>
            </SidebarMenu>
          </SidebarGroup>
        </SidebarContent>
      </Sidebar>
    </SidebarProvider>

Installation

The Control UI source installs this primitive from src/registry/sources/control-ui/ui/sidebar.tsx.

Registry command
See registry manifest

Dependencies

Mobile hooksrc/registry/hooks/use-mobile.tsHook
Sidebar menussrc/registry/sources/control-ui/ui/sidebar-menu.tsxSupport
Mobile navigationsrc/registry/sources/control-ui/ui/sidebar-mobile.tsxSupport
Sidebar statesrc/registry/sources/control-ui/ui/sidebar-provider.tsxSupport
Sidebar resizingsrc/registry/sources/control-ui/ui/sidebar-resize-rail.tsxSupport
Sheet slotsrc/registry/sources/control-ui/ui/sheet.tsxSkin
ButtonbuttonPrimitiveDrawerdrawerPrimitiveScroll areascroll-areaPrimitiveTooltiptooltipPrimitiveTrack highlighttrack-highlightPrimitive

Raw code

Primary installed source

src/registry/sources/control-ui/ui/sidebar.tsx

Knobs

Typed custom properties the recipe paints with. Set one on the root — style, a utility class, or a skin — and every slot inherits it.

--cui-sidebar-* · 59 knobsHow the cascade resolves
--cui-sidebar-nav-height<length-percentage>4.5rem
--cui-sidebar-nav-background<color>var(--sidebar)
--cui-sidebar-nav-foreground<color>var(--sidebar-foreground)
--cui-sidebar-nav-border-color<color>var(--sidebar-border)
--cui-sidebar-nav-border-width<length>var(--control-rim-width)
--cui-sidebar-nav-shadow*var(--shadow-md)
--cui-sidebar-nav-item-radius<length-percentage>var(--radius-control)
--cui-sidebar-nav-item-active-background<color>var(--active-fill)
--cui-sidebar-nav-item-active-foreground<color>var(--foreground)
--cui-sidebar-nav-gap<length-percentage>calc(var(--spacing) * 3)
--cui-sidebar-menu-badge-height<length-percentage>calc(var(--spacing) * 5)
--cui-sidebar-menu-badge-background<color>oklch(from var(--sidebar-foreground) l c h / 0.08)
--cui-sidebar-menu-badge-foreground<color>var(--sidebar-foreground)
--cui-sidebar-rail-divider-background<color>transparent
--cui-sidebar-wrapper-background<color>transparent
--cui-sidebar-wrapper-background-image*none
--cui-sidebar-inner-background<color>var(--sidebar)
--cui-sidebar-inner-border-color<color>var(--sidebar-border)
--cui-sidebar-inner-background-image*none
--cui-sidebar-inner-backdrop-filter*none
--cui-sidebar-inner-border-width<length>0px
--cui-sidebar-inner-radius<length-percentage>0px
--cui-sidebar-inner-shadow*none
--cui-sidebar-header-background<color>transparent
--cui-sidebar-header-shadow*0 0 transparent
--cui-sidebar-inset-background<color>var(--background)
--cui-sidebar-inset-background-image*none
--cui-sidebar-inset-shadow*var(--shadow-sm)
--cui-sidebar-inset-border-color<color>var(--sidebar-border)
--cui-sidebar-inset-border-width<length>0px
--cui-sidebar-inset-radius<length-percentage>var(--radius-xl)
--cui-sidebar-inset-inset<length-percentage>calc(var(--spacing) * 2)
--cui-sidebar-menu-button-radius<length-percentage>var(--radius-popup-item)
--cui-sidebar-group-label-foreground<color>oklch(from var(--sidebar-foreground) l c h / 0.7)
--cui-sidebar-menu-button-foreground<color>var(--sidebar-foreground)
--cui-sidebar-menu-button-hover-background<color>var(--hover-fill)
--cui-sidebar-menu-button-hover-foreground<color>var(--foreground)
--cui-sidebar-menu-button-hover-shadow*0 0 transparent
--cui-sidebar-menu-button-active-background<color>var(--active-fill)
--cui-sidebar-menu-button-active-foreground<color>var(--foreground)
--cui-sidebar-menu-button-active-shadow*0 0 transparent
--cui-sidebar-menu-button-track-active-foreground<color>var(--foreground)
--cui-sidebar-menu-button-height<length-percentage>var(--control-h-md)
--cui-sidebar-menu-button-padding-block<length-percentage>0px
--cui-sidebar-menu-button-padding-inline<length-percentage>calc(var(--sidebar-width-icon) / 2 - var(--spacing) * 4)
--cui-sidebar-menu-button-font-size<length-percentage>var(--text-body)
--cui-sidebar-menu-button-font-weight<number>400
--cui-sidebar-menu-button-active-font-weight<number>500
--cui-sidebar-group-label-font-size<length-percentage>var(--text-caption)
--cui-sidebar-group-label-font-weight<number>500
--cui-sidebar-group-label-letter-spacing<length>0em
--cui-sidebar-group-label-text-transformnone | uppercase | lowercase | capitalizenone
--cui-sidebar-group-label-padding<length-percentage>+0px calc(var(--spacing) * 2)
--cui-sidebar-skip-link-background<color>var(--background)
--cui-sidebar-skip-link-foreground<color>var(--foreground)
--cui-sidebar-skip-link-radius<length-percentage>var(--radius-control)
--cui-sidebar-skip-link-shadow*var(--shadow-md)
--cui-sidebar-menu-button-background<color>transparent
--cui-sidebar-menu-button-shadow*0 0 transparent