Skip to the content
BooleanPress UI

Menu

Sidebar

The navigation column of an admin screen: groups of links that collapse to icons or hide entirely.

Import

import { Sidebar, SidebarContent, SidebarFooter, SidebarGroup, SidebarGroupAction, SidebarGroupContent, SidebarGroupLabel, SidebarHeader, SidebarInput, SidebarInset, SidebarMenu, SidebarMenuAction, SidebarMenuBadge, SidebarMenuButton, SidebarMenuItem, SidebarMenuSkeleton, SidebarMenuSub, SidebarMenuSubButton, SidebarMenuSubItem, SidebarProvider, SidebarRail, SidebarSeparator, SidebarTrigger } from "@booleanpress/ui/sidebar"

Usage

A sidebar is a family of parts. SidebarProvider holds the state and wraps both the Sidebar and the page beside it, SidebarInset. Put a SidebarTrigger where people expect the toggle, usually in the page header.

import {
  Sidebar,
  SidebarContent,
  SidebarGroup,
  SidebarGroupLabel,
  SidebarInset,
  SidebarMenu,
  SidebarMenuButton,
  SidebarMenuItem,
  SidebarProvider,
  SidebarTrigger,
} from "@booleanpress/ui/sidebar"

export function AdminLayout({ children }: { children: React.ReactNode }) {
  return (
    <SidebarProvider>
      <Sidebar collapsible="icon">
        <SidebarContent>
          <SidebarGroup>
            <SidebarGroupLabel>Delivery</SidebarGroupLabel>
            <SidebarMenu>
              <SidebarMenuItem>
                <SidebarMenuButton asChild isActive tooltip="Email log">
                  <a href="/logs">Email log</a>
                </SidebarMenuButton>
              </SidebarMenuItem>
            </SidebarMenu>
          </SidebarGroup>
        </SidebarContent>
      </Sidebar>
      <SidebarInset>
        <header>
          <SidebarTrigger />
        </header>
        {children}
      </SidebarInset>
    </SidebarProvider>
  )
}

collapsible sets what the toggle does: "icon" shrinks it to a rail of icons (give each SidebarMenuButton a tooltip and keep the label in a span), "offcanvas" (the default) slides it out of view, "none" makes it fixed. side is left or right; variant is sidebar, floating or inset.

The open or collapsed state is kept in the browser's local storage under the provider's sidebarStorageKey (one key per product), and read back on the next visit. It starts open unless defaultOpen={false}; a saved choice wins over the default. On the first render the state is defaultOpen, and the saved value applies right after. Control it with open and onOpenChange. useSidebar() returns state, open, setOpen, isMobile, openMobile, setOpenMobile and toggleSidebar. Ctrl+B or ⌘B toggles it. Below 768 px wide the sidebar becomes a sheet opened by the same trigger.

The sidebar positions itself against the window (position: fixed) and the page is at least a window tall, so it belongs in the app's outer layout, once.

Examples

Admin layout

Icon-collapsible sidebar with groups, an active item, a badge, a collapsible submenu, and the trigger in the page header.

Starting collapsed

defaultOpen={false} starts it as a rail of icons until someone opens it.

On the right

side="right" with collapsible="offcanvas": a detail panel that slides out of view.

Accessibility

Semantics
A plain container: put the links in a nav yourself by passing asChild to SidebarMenuButton with a link, or wrap the sidebar's content in a nav with a name. SidebarMenuButton renders a button; isActive sets data-active, not aria-current. Menus are ul lists. On narrow windows the sidebar is a modal dialog (a sheet) named from the provider's sidebarTitle and described by sidebarDescription.
Labels
The trigger and the rail are named from the provider's toggleSidebar string. An icon-only button in the collapsed rail is named by its text, which stays in the DOM (hidden visually), and by its tooltip. Pass aria-current="page" yourself on the link of the current page.
Focus
The trigger and every menu button are in the tab order. The rail is a mouse affordance and is not focusable (tabIndex={-1}). On narrow windows the sheet moves focus in, keeps Tab inside and returns focus to the trigger.
Known limits
  • isActive only styles the item; it does not set aria-current. Set it on the link yourself.
  • It positions itself against the window, so it cannot sit inside a smaller panel. The examples render each in its own frame.
  • A tooltip shows only in the collapsed rail, never when the sidebar is open or on narrow windows.

Keyboard

Keyboard
KeyBehaviour
CtrlBToggles the sidebar from anywhere on the page. ⌘B on macOS.
EnterSpaceOn the trigger or a menu button, presses it.
TabMoves through the trigger and the menu buttons in order.
EscapeOn narrow windows, closes the sheet and returns focus to the trigger.

API

Sidebar

Renders a div and passes it every other prop.

Sidebar props
PropTypeDefaultDescription
collapsible"none" | "icon" | "offcanvas"offcanvasWhat the toggle does: offcanvas slides it out of view, icon leaves a rail of icons, none keeps it fixed.
side"left" | "right"leftleft or right. left by default.
variant"inset" | "sidebar" | "floating"sidebarsidebar (full height, a border), floating (a card with a margin) or inset (the page sits in a card beside it).

SidebarContent

Renders a div and passes it every other prop.

SidebarFooter

Renders a div and passes it every other prop.

SidebarGroup

Renders a div and passes it every other prop.

SidebarGroupAction

Renders a button and passes it every other prop.

SidebarGroupAction props
PropTypeDefaultDescription
asChildbooleanfalseRender the child element instead, with this part's classes merged onto it.

SidebarGroupContent

Renders a div and passes it every other prop.

SidebarGroupLabel

Renders a div and passes it every other prop.

SidebarGroupLabel props
PropTypeDefaultDescription
asChildbooleanfalseRender the child element instead, with this part's classes merged onto it.

SidebarHeader

Renders a div and passes it every other prop.

SidebarInput

SidebarInset

Renders a main and passes it every other prop.

SidebarMenu

Renders a ul and passes it every other prop.

SidebarMenuAction

Renders a button and passes it every other prop.

SidebarMenuAction props
PropTypeDefaultDescription
asChildbooleanfalseRender the child element instead, with this part's classes merged onto it.
showOnHoverbooleanfalseShow it only while the row is hovered or has focus.

SidebarMenuBadge

Renders a div and passes it every other prop.

SidebarMenuButton

Renders a button and passes it every other prop.

SidebarMenuButton props
PropTypeDefaultDescription
asChildbooleanfalseRender the child element instead, such as a link, with this part's classes merged onto it.
isActivebooleanfalseMarks the current page (data-active) and highlights it.
tooltipstring | (TooltipContentProps & RefAttributes<HTMLDivElement>)Text, or TooltipContent props, shown beside the button while the sidebar is collapsed to icons.

SidebarMenuItem

Renders a li and passes it every other prop.

SidebarMenuSkeleton

Renders a div and passes it every other prop.

SidebarMenuSkeleton props
PropTypeDefaultDescription
showIconbooleanfalseDraw a square where the icon goes.

SidebarMenuSub

Renders a ul and passes it every other prop.

SidebarMenuSubButton

Renders a a and passes it every other prop.

SidebarMenuSubButton props
PropTypeDefaultDescription
asChildbooleanfalseRender the child element instead, such as a router link.
isActivebooleanfalseMarks the current page and highlights it.
size"sm" | "md"mdsm or md. md by default.

SidebarMenuSubItem

Renders a li and passes it every other prop.

SidebarProvider

Renders a div and passes it every other prop.

SidebarProvider props
PropTypeDefaultDescription
defaultOpenbooleantrueWhether it starts open. true by default; a choice saved in local storage wins.
onOpenChange((open: boolean) => void)Called with true or false when it opens or collapses.
openbooleanWhether it is open, when you control it. Pair it with onOpenChange.

SidebarRail

Renders a button and passes it every other prop.

SidebarSeparator

SidebarSeparator props
PropTypeDefaultDescription
asChildboolean
decorativebooleanWhether or not the component is purely decorative. When true, accessibility-related attributes are updated so that that the rendered element is removed from the accessibility tree.
orientation"horizontal" | "vertical"verticalEither vertical or horizontal. Defaults to horizontal.

SidebarTrigger

SidebarTrigger props
PropTypeDefaultDescription
asChildboolean

Also exported: useSidebar, a hook for the parts' shared state; call it inside the component's provider.

Every part takes className, merged with its defaults by cn(), and ref, which reaches the element it renders.

Data attributes: data-slot="sidebar" (Sidebar), data-slot="sidebar-content" (SidebarContent), data-slot="sidebar-footer" (SidebarFooter), data-slot="sidebar-group" (SidebarGroup), data-slot="sidebar-group-action" (SidebarGroupAction), data-slot="sidebar-group-content" (SidebarGroupContent), data-slot="sidebar-group-label" (SidebarGroupLabel), data-slot="sidebar-header" (SidebarHeader), data-slot="sidebar-input" (SidebarInput), data-slot="sidebar-inset" (SidebarInset), data-slot="sidebar-menu" (SidebarMenu), data-slot="sidebar-menu-action" (SidebarMenuAction), data-slot="sidebar-menu-badge" (SidebarMenuBadge), data-slot="sidebar-menu-button" (SidebarMenuButton), data-slot="sidebar-menu-item" (SidebarMenuItem), data-slot="sidebar-menu-skeleton" (SidebarMenuSkeleton), data-slot="sidebar-menu-sub" (SidebarMenuSub), data-slot="sidebar-menu-sub-button" (SidebarMenuSubButton), data-slot="sidebar-menu-sub-item" (SidebarMenuSubItem), data-slot="sidebar-wrapper" (SidebarProvider), data-slot="sidebar-rail" (SidebarRail), data-slot="sidebar-separator" (SidebarSeparator), data-slot="sidebar-trigger" (SidebarTrigger), and data-state, data-collapsible, data-variant, data-side, data-size, data-active.

Provider strings: sidebarTitle, sidebarDescription, toggleSidebar (BooleanUIProvider's strings).

Theming

The sidebar has its own tokens: --sidebar, --sidebar-foreground, --sidebar-accent, --sidebar-accent-foreground, --sidebar-border and --sidebar-ring. Width is --sidebar-width (16rem) and --sidebar-width-icon (3rem) on the provider; override them with style. On narrow windows the sheet is 18rem wide.

The tokens its classes read. Change their values in your theme, and it follows.

Theme tokens
TokenUsed for
--backgroundbackground
--sidebarbackground
--sidebar-accentbackground
--sidebar-accent-foregroundtext
--sidebar-borderborder, background
--sidebar-foregroundtext
--sidebar-ringfocus ring