Skip to content

Sidebar

Lays out an app's side navigation, with groups, a header and a footer. Collapses, and becomes a sheet on small screens.

Library
What’s this?
Created by Me

The reports in this view, most recent first.

Installation

pnpm dlx shadcn@latest add @wisty/sidebar

The CLI installs the Radix or Base UI file to match your project, with what it builds on: button, input, separator, sheet, skeleton, tooltip.

If your project doesn't have Wisty UI's theme yet, add it once with npx shadcn@latest add @wisty/cosmos. Until then, components keep shadcn's default look. Installation has the details.

When to use

Use it

  • For the navigation of an app: its objects, pages or Setup sections, in groups with titles.
  • For a list of destinations that stays on screen while the content changes, like Reports' folders or Object Manager's objects.
  • For an app shell that collapses its navigation to make room and moves it into a sheet on small screens.
  • For navigation inside one page, between the sections of a settings page or a folder list beside its content, with collapsible="none", as the In a page example does.

Don't use it

  • To switch between views of one record or page. Use Tabs.
  • For a short list of links at the top of a page. Use Breadcrumb.
  • For a menu of commands that opens from a button. Use Dropdown Menu.
  • For a deep tree of nodes that expand in place, like Setup's object tree. Use Tree.

Usage

SidebarProvider
├── Sidebar
│   ├── SidebarHeader
│   ├── SidebarContent
│   │   └── SidebarGroup
│   │       ├── SidebarGroupLabel
│   │       └── SidebarGroupContent
│   │           └── SidebarMenu
│   │               └── SidebarMenuItem
│   │                   ├── SidebarMenuButton
│   │                   ├── SidebarMenuBadge
│   │                   └── SidebarMenuSub
│   │                       └── SidebarMenuSubItem
│   │                           └── SidebarMenuSubButton
│   └── SidebarFooter
└── SidebarInset
    └── SidebarTrigger

Each SidebarMenuButton holds an icon and then its label in a span, last, so the label truncates.

import { HomeIcon } from "lucide-react"

import { SidebarMenuButton, SidebarMenuItem } from "@/components/ui/sidebar"
<SidebarMenuItem>
  <SidebarMenuButton isActive aria-current="page">
    <HomeIcon />
    <span>Home</span>
  </SidebarMenuButton>
</SidebarMenuItem>

The examples hold the selected row in state, so a click selects it; an app derives the active item from its route and sets isActive and aria-current="page" on the current page's row.

collapsible on Sidebar sets how it closes: offcanvas slides it off screen, icon shrinks it to an icon rail, and none keeps it open at every width, with no sheet on small screens. A button's tooltip shows only on the icon rail. showOnHover on SidebarMenuAction hides the action until its row is hovered or focused, on screens 768px and wider.

Items are buttons. For an item that goes to a page, pass asChild with an <a> in Radix projects, or render={<a href="…" />} in Base UI projects, as the Links example does; SidebarMenuSubButton is a link already. In Radix projects, items with a tooltip need a TooltipProvider around the app.

The provider writes its state to a sidebar_state cookie but never reads it. Read the cookie on the server and pass it as defaultOpen, or the sidebar opens on every load.

The app shell's provider goes around the whole app, once. A page's own navigation gets its own SidebarProvider around just that region, with className="min-h-0" so it doesn't fill the screen, and a Sidebar with collapsible="none", which never collapses or moves into a sheet. It has no border of its own: add border-e border-sidebar-border on the side that meets the content, or variant="inset" for the shaded nav on a gray region. Every provider on the page listens for Ctrl+B and writes the same cookie. A collapsible="none" sidebar ignores its open state, so leave SidebarTrigger out of the region.

Examples

Items that link to pages, through `asChild` on Radix or `render` on Base UI, the current page set with `isActive` and `aria-current="page"`.

Accounts

Accounts you viewed recently.

In a page

Navigation between the sections of one page, beside their content, with `collapsible="none"` on `Sidebar` and `min-h-0` on the provider.

Language & Time Zone

The language, locale and time zone that dates, numbers and currencies show in.

Shaded

The nav on the gray page beside a white content panel, set with `variant="inset"` on `Sidebar`.

Created by Me

The reports in this view, most recent first.

Compact

Small rows for a long list in little vertical space, set with `size="sm"` on each `SidebarMenuButton`.

Profiles

Settings for the selected Setup page.

Quick Find

A `SidebarInput` in `SidebarHeader` that filters the items, as Setup's Quick Find.

Users

Type in Quick Find to filter the Setup menu.

Show more

A section that shows its first items and a Show more row that reveals the rest, as SLDS's overflow.

Recent

The reports in this view, most recent first.

Nested items and actions

A `SidebarMenuSub` under an item, a `SidebarMenuAction` on rows, a `SidebarGroupAction` on a title, and a `SidebarSeparator`.

Page Layouts

Hover an object for its actions.

Icon rail

Collapsed with `collapsible="icon"` on `Sidebar`, each button naming itself with `tooltip`, under an app header and over a footer menu.

Accounts

Press the trigger, or the sidebar's edge, to expand it. Hover an icon for its name.

API reference

PartBuilt on
SidebarProvider<div> (opens in a new tab)
Sidebar<div> (opens in a new tab)
SidebarTriggerButton
SidebarRail<button> (opens in a new tab)
SidebarInset<main> (opens in a new tab)
SidebarInputInput
SidebarHeader<div> (opens in a new tab)
SidebarFooter<div> (opens in a new tab)
SidebarSeparatorSeparator
SidebarContent<div> (opens in a new tab)
SidebarGroup<div> (opens in a new tab)
SidebarGroupLabel<div> (opens in a new tab)
SidebarGroupAction<button> (opens in a new tab)
SidebarGroupContent<div> (opens in a new tab)
SidebarMenu<ul> (opens in a new tab)
SidebarMenuItem<li> (opens in a new tab)
SidebarMenuButton<button> (opens in a new tab)
SidebarMenuAction<button> (opens in a new tab)
SidebarMenuBadge<div> (opens in a new tab)
SidebarMenuSkeleton<div> (opens in a new tab)
SidebarMenuSub<ul> (opens in a new tab)
SidebarMenuSubItem<li> (opens in a new tab)
SidebarMenuSubButton<a> (opens in a new tab)

SidebarProvider

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

Sidebar

PropTypeDefault
side"left" | "right""left"
variant"sidebar" | "floating" | "inset""sidebar"
collapsible"offcanvas" | "icon" | "none""offcanvas"

SidebarGroupLabel

PropTypeDefaultDescription
asChildbooleanfalseRenders the child element in its place, with the props merged onto it.

SidebarGroupAction

PropTypeDefaultDescription
asChildbooleanfalseRenders the child element in its place, with the props merged onto it.

SidebarMenuButton

PropTypeDefaultDescription
asChildbooleanfalseRenders the child element in its place, with the props merged onto it.
isActivebooleanfalse
tooltipstring | React.ComponentProps<typeof TooltipContent>
variant"default" | "outline""default"
size"default" | "sm" | "lg""default"

SidebarMenuAction

PropTypeDefaultDescription
asChildbooleanfalseRenders the child element in its place, with the props merged onto it.
showOnHoverbooleanfalse

SidebarMenuSkeleton

PropTypeDefault
showIconbooleanfalse

SidebarMenuSubButton

PropTypeDefaultDescription
asChildbooleanfalseRenders the child element in its place, with the props merged onto it.
size"sm" | "md""md"
isActivebooleanfalse

useSidebarhook

Returns SidebarContextProps.

SidebarContextPropstype

MemberTypeDefault
state*"expanded" | "collapsed"
open*boolean
setOpen*(open: boolean) => void
openMobile*boolean
setOpenMobile*(open: boolean) => void
isMobile*boolean
toggleSidebar*() => void

SLDS mapping

The shell is shadcn's. The list inside it is SLDS 2's vertical navigation, slds-nav-vertical.

  • SidebarGroup is slds-nav-vertical__section and SidebarGroupLabel is slds-nav-vertical__title.
  • SidebarMenuButton is slds-nav-vertical__action. Rows in SidebarHeader or SidebarFooter aren't navigation, so they don't take its states.
  • SidebarMenuBadge is slds-badge at the end of the row.
  • SidebarMenuSubButton is the same row indented one level, as SLDS's tree indents.

Known gaps:

  • SLDS's nav doesn't collapse, float, nest or have a header or footer. floating, collapsible="icon", SidebarRail, SidebarHeader, SidebarFooter, SidebarMenuAction, SidebarGroupAction, SidebarMenuSub and SidebarMenuSkeleton are shadcn's, built from the theme tokens.
  • SLDS's Show more row is a pattern, not a part. Build it from a SidebarMenuButton with aria-expanded and aria-controls after the list, as the Show more example does.
  • Icons are lucide's, not SLDS's utility icons.
PropValueSLDSNotes
Sidebar variantsidebarslds-nav-verticalThe base look: a white nav with a border on its page side.
Sidebar variantinsetslds-nav-vertical_shadeSLDS's shaded nav: the nav sits on the gray page, the content is a white panel, and hovered and active rows are white, the active one with a line above and below.
Sidebar variantfloatingnoneshadcn's floating card, built from the tokens.
Sidebar collapsibleoffcanvasnoneThe nav slides off screen. SLDS's vertical navigation doesn't collapse.
Sidebar collapsibleiconnoneCollapses to an icon rail with tooltips. No SLDS counterpart.
Sidebar collapsiblenoneslds-nav-verticalA fixed nav that never collapses, like Setup's, or a page's own navigation beside its content.
SidebarMenuButton variantdefaultslds-nav-vertical__action
SidebarMenuButton variantoutlinenoneA bordered row on the container color. No SLDS counterpart.
SidebarMenuButton sizedefaultslds-nav-vertical__actionSLDS's nav row.
SidebarMenuButton sizesmslds-nav-vertical_compactSLDS's compact nav: shorter rows with less room at each end. A group's title tightens with its compact rows.
SidebarMenuButton sizelgnoneA tall row for an avatar and two lines of text, as in a header or footer menu, with its avatar lined up with the section titles.
SidebarMenuButton isActivetrueslds-is-activeThe active bar, color and weight. Add aria-current="page" too.
SidebarMenuSubButton sizemdnoneSLDS has no nested items. The same row, its text one tree level past the parent's label.
SidebarMenuSubButton sizesmnoneThe indented row at compact height.

Keyboard interactions

Keys Action
Ctrl+B, Cmd+B Toggles the sidebar from anywhere on the page.
Escape On a small screen, closes the sheet. On the icon rail, closes an open tooltip.

Accessibility

  • Wrap the groups in a nav with an aria-label so the navigation is a landmark. Sidebar itself is a div.
  • Set aria-current="page" next to isActive on the current page's item. isActive only sets the look.
  • Say what a badge counts in the item's name. SidebarMenuBadge sits beside the button, so its number isn't part of the name: put the count and its meaning in an sr-only span inside the label's span (: 3 new items), and mark the badge aria-hidden, as the Reports navigation example does.
  • Give SidebarMenuAction, SidebarGroupAction and SidebarInput an aria-label.
  • On the icon rail, each item's tooltip names it on hover and focus. Keep the label in the item too; it's the item's name.
  • On a small screen the sidebar is a modal dialog with its close button hidden. Escape, the backdrop and the trigger close it.
  • In Lightning Blue, SidebarMenuBadge has 3.65:1 contrast, below WCAG AA's 4.5:1 for its text size. Salesforce's own badge has the same limitation.

Motion

  • Collapsing and expanding slides the nav over 200ms, in every theme. On a small screen the sheet slides in as Sheet does.
  • With reduced motion nothing slides. The sheet's backdrop still fades.

Responsive behavior

  • The sidebar is 16rem wide, 3rem as an icon rail and 18rem in the sheet. Set --sidebar-width and --sidebar-width-icon on the provider's style to change the first two. The sheet's width is fixed.
  • Below 768px the trigger opens a sheet instead. The breakpoint lives in the use-mobile hook and in the md: classes; change both together.
  • SidebarContent scrolls; the header and footer stay put.

Performance

  • Every item renders up front. Hundreds are fine; for thousands, filter them as the Quick Find example does.
  • On the server the desktop sidebar renders, hidden on a small screen, and the sheet takes over after hydration.

Rules

  • Use variant="inset" for the shaded nav and size="sm" on the buttons for the compact nav. Don't restyle rows, bars or tints with utility classes.
  • Make an item a link when it goes to a page, and a button when it changes something on the page, as Show more does.
  • Use size="lg" for a row in the header or footer, so its avatar lines up with the section titles.
  • Use lucide icons as bare children. Don't import Salesforce icons into a primitive.