Sidebar
An app sidebar that collapses to icons or off the canvas and becomes a sheet on phones.
App sidebar
Collapses off the canvas. The trigger, the rail and Cmd or Ctrl + B toggle it; on a phone it opens as a sheet. Hover a project for its action; Shared is a disabled row.
Code
<SidebarProvider>
<Sidebar>
<SidebarHeader>...</SidebarHeader>
<SidebarContent>
<SidebarGroup>
<SidebarGroupLabel>Application</SidebarGroupLabel>
<SidebarGroupContent>
<SidebarMenu>
<SidebarMenuItem e:for={item of items}>
<SidebarMenuButton href={item.url} isActive={item.url === path}>
<Icon name={item.icon} /><span>{item.title}</span>
</SidebarMenuButton>
<SidebarMenuBadge e:if={item.badge}>{item.badge}</SidebarMenuBadge>
</SidebarMenuItem>
<SidebarMenuItem>
<SidebarMenuButton disabled><Icon name="folder" /><span>Shared</span></SidebarMenuButton>
</SidebarMenuItem>
</SidebarMenu>
</SidebarGroupContent>
</SidebarGroup>
</SidebarContent>
<SidebarFooter>...</SidebarFooter>
<SidebarRail />
</Sidebar>
<SidebarInset>
<header><SidebarTrigger /></header>
...
</SidebarInset>
</SidebarProvider>Usage
import {
SidebarProvider, Sidebar, SidebarHeader, SidebarContent, SidebarFooter,
SidebarGroup, SidebarGroupLabel, SidebarGroupContent,
SidebarMenu, SidebarMenuItem, SidebarMenuButton,
SidebarRail, SidebarInset, SidebarTrigger,
} from "@elements/ui/sidebar";
<SidebarProvider>
<Sidebar collapsible="icon">
<SidebarContent>
<SidebarGroup>
<SidebarGroupLabel>Application</SidebarGroupLabel>
<SidebarGroupContent>
<SidebarMenu>
<SidebarMenuItem e:for={item of items}>
<SidebarMenuButton href={item.url} isActive={item.url === path} tooltip={item.title}>
<Icon name={item.icon} /><span>{item.title}</span>
</SidebarMenuButton>
</SidebarMenuItem>
</SidebarMenu>
</SidebarGroupContent>
</SidebarGroup>
</SidebarContent>
<SidebarRail />
</Sidebar>
<SidebarInset>
<header><SidebarTrigger /></header>
...
</SidebarInset>
</SidebarProvider>Examples
Collapsible to icons
collapsible icon keeps a rail of icons; each menu button shows its tooltip while collapsed.
Code
<Sidebar collapsible="icon">
...
<SidebarMenuButton tooltip="Inbox"><Icon name="inbox" /><span>Inbox</span></SidebarMenuButton>
</Sidebar>Floating and inset
variant floating lifts the panel off the edge; inset makes the page a raised card beside it.
Code
<Sidebar variant="floating">...</Sidebar>
<Sidebar variant="inset">...</Sidebar> <!-- SidebarInset becomes a raised card -->Right side
side right docks it to the other edge; tooltips open to the left.
Code
<Sidebar side="right" collapsible="icon">...</Sidebar>Right to left
Under dir rtl the sidebar docks to the start edge with side right; the rail moves with it and the trigger icon mirrors.
Code
<SidebarProvider attrs={{ dir: "rtl" }}>
<Sidebar dir="rtl" side="right">
...
<SidebarRail />
</Sidebar>
<SidebarInset>
<header><SidebarTrigger /></header>
</SidebarInset>
</SidebarProvider>Collapsible groups and submenus
Compose with Collapsible: a group label or a menu button becomes its trigger. SidebarMenuSub nests items.
Code
<Collapsible open={true}>
<SidebarGroup>
<CollapsibleTrigger>
<SidebarGroupLabel button>Help <Icon name="chevron-right" /></SidebarGroupLabel>
</CollapsibleTrigger>
<CollapsibleContent>
<SidebarGroupContent>...</SidebarGroupContent>
</CollapsibleContent>
</SidebarGroup>
</Collapsible>
<SidebarMenuItem>
<Collapsible>
<CollapsibleTrigger>
<SidebarMenuButton><Icon name="file" /><span>Docs</span></SidebarMenuButton>
</CollapsibleTrigger>
<CollapsibleContent>
<SidebarMenuSub>
<SidebarMenuSubItem><SidebarMenuSubButton href="/docs/intro">Introduction</SidebarMenuSubButton></SidebarMenuSubItem>
</SidebarMenuSub>
</CollapsibleContent>
</Collapsible>
</SidebarMenuItem>Menus in the sidebar
A DropdownMenu takes a menu button, a menu action or a group action as its trigger, with or without a Tooltip around it: a team switcher in the header, New project beside the group label, a project's actions, the account in the footer. The row stays highlighted while its menu is open, and in icon mode the menus open beside the rail.
Code
<SidebarHeader>
<SidebarMenu>
<SidebarMenuItem>
<DropdownMenu side="right" align="start" label="Teams">
<SidebarMenuButton slot="trigger" size="lg" tooltip="Acme Inc">...</SidebarMenuButton>
<DropdownMenuLabel>Teams</DropdownMenuLabel>
<DropdownMenuItem e:for={team of teams} onSelect={() => state.team = team.name}>{team.name}</DropdownMenuItem>
</DropdownMenu>
</SidebarMenuItem>
</SidebarMenu>
</SidebarHeader>
...
<SidebarGroupLabel>Projects</SidebarGroupLabel>
<DropdownMenu side="right" align="start">
<Tooltip slot="trigger" content="New project" side="right">
<SidebarGroupAction slot="trigger" label="New project"><Icon name="plus" /></SidebarGroupAction>
</Tooltip>
<DropdownMenuItem>Blank project</DropdownMenuItem>
<DropdownMenuItem>Import</DropdownMenuItem>
</DropdownMenu>
<SidebarMenuItem>
<SidebarMenuButton href="/design"><Icon name="image" /><span>Design</span></SidebarMenuButton>
<DropdownMenu side="right" align="start">
<SidebarMenuAction slot="trigger" label="More for Design" showOnHover><Icon name="more-horizontal" /></SidebarMenuAction>
<DropdownMenuItem>View project</DropdownMenuItem>
<DropdownMenuItem>Share project</DropdownMenuItem>
</DropdownMenu>
</SidebarMenuItem>
...
<SidebarFooter>
<SidebarMenu>
<SidebarMenuItem>
<DropdownMenu side="right" align="end" label="Account">
<SidebarMenuButton slot="trigger" size="lg" tooltip="Account">...</SidebarMenuButton>
<DropdownMenuItem>Account</DropdownMenuItem>
<DropdownMenuItem>Log out</DropdownMenuItem>
</DropdownMenu>
</SidebarMenuItem>
</SidebarMenu>
</SidebarFooter>Controlled
Bind open to read or set it; onOpenChange fires when the sidebar toggles itself. cookie remembers it across reloads.
Code
<!-- state = { open: readSidebarCookie("sidebar_state") ?? true }: the cookie read back -->
<SidebarProvider open={state.open} onOpenChange={(open) => save(open)} cookie="sidebar_state">
...
</SidebarProvider>
<Button onclick={() => state.open = !state.open}>Toggle</Button>Loading
SidebarMenuSkeleton holds the row's height while items load.
Code
<SidebarMenu>
<SidebarMenuItem e:for={n of [1, 2, 3, 4, 5]}>
<SidebarMenuSkeleton showIcon />
</SidebarMenuItem>
</SidebarMenu>Phone breakpoint
mobileBreakpoint sets the width under which the sidebar becomes a sheet (768 by default). The first opens the sheet only under 600px, and with focus inside it Ctrl+B toggles it rather than the first sidebar on the page; the second is an inset layout that becomes a sheet under 1024px and drops the card.
Code
<!-- The sheet takes over under 600px instead of 768px. -->
<SidebarProvider mobileBreakpoint={600}>...</SidebarProvider>
<!-- An inset page is a card above 1024px, and the sidebar a sheet below it. -->
<SidebarProvider mobileBreakpoint={1024}>
<Sidebar variant="inset">...</Sidebar>
<SidebarInset>...</SidebarInset>
</SidebarProvider>Theming and a custom trigger
The theme variables --sidebar, --sidebar-foreground, --sidebar-primary, --sidebar-accent, --sidebar-border and --sidebar-ring theme the panel; --sidebar-width sets its width. Projects hides in icon mode through data-collapsible. The active row shows its action without hover. The button is a custom trigger calling toggleSidebar.
Code
<!-- the sidebar's theme variables and width, set by a class on the provider -->
<SidebarProvider class="brand">
<Sidebar collapsible="icon">
...
<SidebarGroup class="hide-in-icons">...</SidebarGroup>
</Sidebar>
<SidebarInset>
<!-- a custom trigger: useSidebar().toggleSidebar -->
<Button onclick={(e) => toggleSidebar(e.currentTarget as Element)}>Toggle sidebar</Button>
</SidebarInset>
</SidebarProvider>
.brand {
--sidebar: var(--accent-soft);
--sidebar-foreground: var(--ink);
--sidebar-primary: var(--accent-deep);
--sidebar-primary-foreground: var(--bg);
--sidebar-accent: var(--bg);
--sidebar-accent-foreground: var(--ink);
--sidebar-border: var(--accent);
--sidebar-ring: var(--accent);
--sidebar-width: var(--space-56);
}
/* hide a group while collapsed to icons */
.sidebar[data-collapsible="icon"] .hide-in-icons { display: none; }