Sidebar
A Sidebar shows users an app’s navigation in a side panel that can collapse to icons or move off the screen.
When to Use
- for the main navigation of an app: the links to its sections, such as “Clients”, “Projects”, and “Invoices”
- when the sections fall into groups, each with a label such as “Sales” or “Billing”
- when a page needs the width, such as a wide table, with
collapsible="icon"set
When Not to Use
- for the main links of a marketing site (use a Navigation Menu)
- for the views of one record (use Tabs)
- for a panel that opens for one task and closes (use a Sheet)
Anatomy
A Sidebar has a shell, which contains the page, and menus inside the shell.
Shell
| Element | Usage |
|---|---|
| Sidebar | Required |
| Trigger | Optional |
| Inset | Required |
| Header | Optional |
| Content | Required |
| Footer | Optional |
SidebarProvider wraps both halves and stores the open state. SidebarInset is the main element of the page. The figure doesn’t show SidebarRail, a strip along the outer edge of the Sidebar that users click to collapse or expand it.
Menu
| Element | Usage |
|---|---|
| Group label | Optional |
| Group action | Optional |
| Menu button | Required |
| Menu action | Optional |
| Menu badge | Optional |
| Sub-menu button | Optional |
A menu is a ul, and each item is an li that contains one button, with an action or a badge at its end. A Sidebar has three more parts: SidebarInput is an Input, SidebarSeparator is a Separator between regions, and SidebarMenuSkeleton is a placeholder for an item that is loading.
Variants
variant on Sidebar sets how the panel meets the page.
| Variant | Purpose |
|---|---|
sidebar | A page that runs to the screen edge, with a line between it and the panel |
floating | A panel separated from the screen edge, as a surface of its own |
inset | A page framed as a card inside the shell |
Sizes
size on SidebarMenuButton sets the height of an item.
| Size | Height |
|---|---|
sm | 28px |
default | 32px |
lg | 48px |
Use lg for a two-line button, as the preview does in its header and footer.
States
To mark the item of the current page, set isActive on SidebarMenuButton. It also sets aria-current="page". To disable an item, set disabled, or aria-disabled="true" if the item is a link.
Behavior
From 768px, the Sidebar is positioned beside the page and is 256px wide. See Layout for the shell’s measures.
By default, side is left, which is a physical edge. In a right-to-left layout, pass side="right". See Right-to-Left for how to read the direction.
Collapsing
collapsible on Sidebar sets what happens when the Sidebar collapses.
| Value | Collapsed Sidebar |
|---|---|
offcanvas | Moves off the screen, and the page fills the full width. This is the default. |
icon | Narrows to 48px, or to 64px in the floating and inset variants |
none | Never collapses. The Sidebar is a 256px column at every width, with no Sheet. |
When the Sidebar is collapsed to icons, its parts change.
| Part | Collapsed to Icons |
|---|---|
| Menu button | Shows its icon. Its label is clipped. |
| Group label, badge, action, group action, and sub-menu | Hidden |
| Content | Doesn’t scroll |
Give each button an icon as its first child, and set tooltip to its label so that users can identify a collapsed button. The Tooltip opens to the right of a collapsed button and stays hidden while the Sidebar is expanded. Sidebar doesn’t render a TooltipProvider. If there is none around the app, the Tooltip waits 600ms.
Controls
| Control | Detail |
|---|---|
SidebarTrigger | Place it in the page’s header. |
SidebarRail | A strip outside the edge of the Sidebar that users click to collapse or expand it. It is hidden below 640px. |
Below 768px
When useMobile reports a viewport under 768px, the Sidebar opens in a Sheet over the page. The Sidebar isn’t in the markup until the Sheet opens.
The Sheet has its own state, openMobile, which starts closed and isn’t saved to a cookie. The Sheet closes when users press Escape or click the overlay. It stays open when users click a link, so call setOpenMobile(false) when the route changes.
Saved State
When the state changes, SidebarProvider writes true or false to the cookie sidebar_state, which lasts seven days. SidebarProvider doesn’t read the cookie. See Restore the State for how to pass the value back.
Overflow
When the groups are taller than the space between the header and the footer, SidebarContent scrolls, and its scrollbar is hidden. A label is truncated when it is the last span in its button.
Content
| String | Rule | Example | Counterexample |
|---|---|---|---|
| Menu button | The name of the page it opens | “Invoices” | “Go to invoices” |
| Group label | One word that names the group | “Billing” | “Billing pages” |
The Sidebar’s own labels are in labels.sidebar. To translate them, use the Summit Provider.
Accessibility
The parts of a Sidebar are div, ul, and li elements. Only SidebarInset, which is a main element, is a landmark.
Keyboard
| Key | Result |
|---|---|
Ctrl+B, Cmd+B | Collapses or expands the Sidebar. Below 768px, it opens or closes the Sheet. The handler is on window. |
Tab | Moves through the header, each menu button and its action, and the footer, in source order. The rail is skipped. |
Enter or Space on the trigger | Collapses or expands the Sidebar. Below 768px, it opens the Sheet, and focus moves to the first control inside. |
Escape | Below 768px, closes the Sheet and returns focus to the trigger. |
Requirements
A menu action or a group action contains only an icon, so it needs an aria-label for screen readers to announce
it, as WCAG 2.2 SC 4.1.2 Name, Role, Value requires.
No part of the Sidebar is a nav. Set role="navigation" and an aria-label such as “Main” on SidebarContent so that screen readers list the links as a landmark.
Installation
npx shadcn@latest add @summit/sidebarThe CLI also adds @summit/button, @summit/input, @summit/separator, @summit/sheet, @summit/skeleton, @summit/summit-provider, @summit/tooltip, and @summit/use-mobile.
Usage
You handle these things in the application:
- The link that each item renders
- Which item is active
- The state to restore on the next visit
- Closing the Sheet after users click a link
import {
Sidebar,
SidebarContent,
SidebarGroup,
SidebarGroupContent,
SidebarGroupLabel,
SidebarInset,
SidebarMenu,
SidebarMenuButton,
SidebarMenuItem,
SidebarProvider,
SidebarTrigger,
} from '@/components/ui/sidebar';
import { ReceiptIcon } from '@phosphor-icons/react';
<SidebarProvider>
<Sidebar>
<SidebarContent role="navigation" aria-label="Main">
<SidebarGroup>
<SidebarGroupLabel>Billing</SidebarGroupLabel>
<SidebarGroupContent>
<SidebarMenu>
<SidebarMenuItem>
<SidebarMenuButton isActive render={<a href="/invoices" aria-current="page" />}>
<ReceiptIcon />
<span>Invoices</span>
</SidebarMenuButton>
</SidebarMenuItem>
</SidebarMenu>
</SidebarGroupContent>
</SidebarGroup>
</SidebarContent>
</Sidebar>
<SidebarInset>
<header className="flex h-12 items-center gap-2 border-b px-4">
<SidebarTrigger />
</header>
</SidebarInset>
</SidebarProvider>;Collapse to Icons
To collapse the Sidebar to icons, set collapsible="icon" on Sidebar and tooltip on each button.
<Sidebar collapsible="icon">
<SidebarContent>
<SidebarMenu>
<SidebarMenuItem>
<SidebarMenuButton tooltip="Invoices" render={<a href="/invoices" />}>
<ReceiptIcon />
<span>Invoices</span>
</SidebarMenuButton>
</SidebarMenuItem>
</SidebarMenu>
</SidebarContent>
<SidebarRail />
</Sidebar>Restore the State
To restore the state on the next visit, read the cookie sidebar_state on the server, and pass its value to defaultOpen. In this snippet, sidebarState is that value.
<SidebarProvider defaultOpen={sidebarState !== 'false'}>
<Sidebar />
</SidebarProvider>Close the Sheet After a Link
To close the Sheet when users click a link, call setOpenMobile from the link. useSidebar returns it.
import { SidebarMenuButton, useSidebar } from '@/components/ui/sidebar';
function NavLink({ href, children }: { href: string; children: React.ReactNode }) {
const { setOpenMobile } = useSidebar();
return (
<SidebarMenuButton render={<a href={href} />} onClick={() => setOpenMobile(false)}>
{children}
</SidebarMenuButton>
);
}An Action on an Item
To add an action to an item, place a SidebarMenuAction after the button. If you set showOnHover, the action is hidden from 768px until users hover over the item or the item has focus.
<SidebarMenuItem>
<SidebarMenuButton render={<a href="/clients" />}>
<UsersIcon />
<span>Clients</span>
</SidebarMenuButton>
<SidebarMenuAction showOnHover aria-label="Add client">
<PlusIcon />
</SidebarMenuAction>
</SidebarMenuItem>While the Menu Loads
While the menu loads, render a SidebarMenuSkeleton for each item. showIcon adds a square before the bar.
<SidebarMenu>
<SidebarMenuItem>
<SidebarMenuSkeleton showIcon />
</SidebarMenuItem>
</SidebarMenu>Change the Width
To change the width, set --sidebar-width or --sidebar-width-icon through style on SidebarProvider. Below 768px, the Sheet keeps its own width.
<SidebarProvider style={{ '--sidebar-width': '20rem' } as React.CSSProperties}>
<Sidebar />
</SidebarProvider>API Reference
The parts accept the props of the elements they render. SidebarGroupLabel, SidebarGroupAction, SidebarMenuButton, SidebarMenuAction, and SidebarMenuSubButton also accept render. See the Base UI useRender documentation for render.
SidebarProvider
| Prop | Type | Default | Description |
|---|---|---|---|
defaultOpen | boolean | true | Whether the Sidebar starts expanded |
open | boolean | None | The state, controlled by the parent |
onOpenChange | (open: boolean) => void | None | Called when the state changes |
Sidebar
| Prop | Type | Default | Description |
|---|---|---|---|
side | 'left' | 'right' | 'left' | The physical edge the Sidebar is positioned on |
variant | 'sidebar' | 'floating' | 'inset' | 'sidebar' | How the panel meets the page |
collapsible | 'offcanvas' | 'icon' | 'none' | 'offcanvas' | What happens when the Sidebar collapses |
SidebarMenuButton
| Prop | Type | Default | Description |
|---|---|---|---|
isActive | boolean | false | Marks the item of the current page |
size | 'default' | 'sm' | 'lg' | 'default' | The height and the text size |
variant | 'default' | 'outline' | 'default' | outline adds a fill and a ring |
tooltip | string, or the props of TooltipContent | None | The Tooltip of a button that is collapsed to its icon |
SidebarMenuAction
| Prop | Type | Default | Description |
|---|---|---|---|
showOnHover | boolean | false | Hides the action from 768px until the item is hovered or has focus |
SidebarMenuSubButton
| Prop | Type | Default | Description |
|---|---|---|---|
size | 'sm' | 'md' | 'md' | The text size |
isActive | boolean | false | Marks the item of the current page |
SidebarMenuSkeleton
| Prop | Type | Default | Description |
|---|---|---|---|
showIcon | boolean | false | Adds a square before the bar |
useSidebar
useSidebar throws an error when it is called outside a SidebarProvider.
| Value | Type | Content |
|---|---|---|
state | 'expanded' | 'collapsed' | The state from 768px |
open | boolean | The same state as a boolean |
setOpen | (open: boolean) => void | Sets the state and writes the cookie |
openMobile | boolean | Whether the Sheet is open |
setOpenMobile | (open: boolean) => void | Opens or closes the Sheet |
isMobile | boolean | The result of useMobile |
toggleSidebar | () => void | Toggles the Sheet below 768px and the Sidebar from 768px |