Summit
ComponentsNavigation

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

ElementUsage
SidebarRequired
TriggerOptional
InsetRequired
HeaderOptional
ContentRequired
FooterOptional

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.

Sales
ElementUsage
Group labelOptional
Group actionOptional
Menu buttonRequired
Menu actionOptional
Menu badgeOptional
Sub-menu buttonOptional

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.

VariantPurpose
sidebarA page that runs to the screen edge, with a line between it and the panel
floatingA panel separated from the screen edge, as a surface of its own
insetA page framed as a card inside the shell

Sizes

size on SidebarMenuButton sets the height of an item.

SizeHeight
sm28px
default32px
lg48px

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.

ValueCollapsed Sidebar
offcanvasMoves off the screen, and the page fills the full width. This is the default.
iconNarrows to 48px, or to 64px in the floating and inset variants
noneNever collapses. The Sidebar is a 256px column at every width, with no Sheet.

When the Sidebar is collapsed to icons, its parts change.

PartCollapsed to Icons
Menu buttonShows its icon. Its label is clipped.
Group label, badge, action, group action, and sub-menuHidden
ContentDoesn’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

ControlDetail
SidebarTriggerPlace it in the page’s header.
SidebarRailA 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

StringRuleExampleCounterexample
Menu buttonThe name of the page it opens“Invoices”“Go to invoices”
Group labelOne 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

KeyResult
Ctrl+B, Cmd+BCollapses or expands the Sidebar. Below 768px, it opens or closes the Sheet. The handler is on window.
TabMoves through the header, each menu button and its action, and the footer, in source order. The rail is skipped.
Enter or Space on the triggerCollapses or expands the Sidebar. Below 768px, it opens the Sheet, and focus moves to the first control inside.
EscapeBelow 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/sidebar

The 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.

Collapse to Icons
<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.

Restore the State
<SidebarProvider defaultOpen={sidebarState !== 'false'}>
    <Sidebar />
</SidebarProvider>

To close the Sheet when users click a link, call setOpenMobile from the link. useSidebar returns it.

Close the Sheet After a Link
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.

An Action on an Item
<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.

While the Menu Loads
<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.

Change the 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

PropTypeDefaultDescription
defaultOpenbooleantrueWhether the Sidebar starts expanded
openbooleanNoneThe state, controlled by the parent
onOpenChange(open: boolean) => voidNoneCalled when the state changes
PropTypeDefaultDescription
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

PropTypeDefaultDescription
isActivebooleanfalseMarks 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
tooltipstring, or the props of TooltipContentNoneThe Tooltip of a button that is collapsed to its icon

SidebarMenuAction

PropTypeDefaultDescription
showOnHoverbooleanfalseHides the action from 768px until the item is hovered or has focus

SidebarMenuSubButton

PropTypeDefaultDescription
size'sm' | 'md''md'The text size
isActivebooleanfalseMarks the item of the current page

SidebarMenuSkeleton

PropTypeDefaultDescription
showIconbooleanfalseAdds a square before the bar

useSidebar

useSidebar throws an error when it is called outside a SidebarProvider.

ValueTypeContent
state'expanded' | 'collapsed'The state from 768px
openbooleanThe same state as a boolean
setOpen(open: boolean) => voidSets the state and writes the cookie
openMobilebooleanWhether the Sheet is open
setOpenMobile(open: boolean) => voidOpens or closes the Sheet
isMobilebooleanThe result of useMobile
toggleSidebar() => voidToggles the Sheet below 768px and the Sidebar from 768px

On this page