Sidebar
A composable, themeable and customizable sidebar component.
Sidebar is a native SolidJS composition that provides desktop collapse, a mobile sheet, and a consistent set of navigation building blocks. Its mobile sheet uses Kobalte Dialog for modal focus management and keyboard behavior.
import type { LucideIcon } from "lucide-solid";import { AudioWaveform, BadgeCheck, Bell, BookOpen, Bot, ChevronRight, ChevronsUpDown, Command, CreditCard, Folder, Forward, Frame, GalleryVerticalEnd, LogOut, MapIcon, MoreHorizontal, PieChart, Plus, Settings2, Sparkles, SquareTerminal, Trash2,} from "lucide-solid";import { createSignal, For, Show } from "solid-js";import { Dynamic } from "solid-js/web";import { Avatar, AvatarFallback, AvatarImage } from "~/components/ui/avatar";import { Collapsible, CollapsibleContent, CollapsibleTrigger,} from "~/components/ui/collapsible";import { DropdownMenu, DropdownMenuContent, DropdownMenuGroup, DropdownMenuItem, DropdownMenuLabel, DropdownMenuSeparator, DropdownMenuShortcut, DropdownMenuTrigger,} from "~/components/ui/dropdown-menu";import { Sidebar, SidebarContent, SidebarFooter, SidebarGroup, SidebarGroupLabel, SidebarHeader, SidebarInset, SidebarMenu, SidebarMenuAction, SidebarMenuButton, SidebarMenuItem, SidebarMenuSub, SidebarMenuSubButton, SidebarMenuSubItem, type SidebarProps, SidebarProvider, SidebarRail, SidebarTrigger, useSidebar,} from "~/components/ui/sidebar";
// This is sample data.const data = { user: { name: "shadcn", email: "m@example.com", avatar: "https://github.com/shadcn.png", }, teams: [ { name: "Acme Inc", logo: GalleryVerticalEnd, plan: "Enterprise", }, { name: "Acme Corp.", logo: AudioWaveform, plan: "Startup", }, { name: "Evil Corp.", logo: Command, plan: "Free", }, ], navMain: [ { title: "Playground", url: "#", icon: SquareTerminal, isActive: true, items: [ { title: "History", url: "#", }, { title: "Starred", url: "#", }, { title: "Settings", url: "#", }, ], }, { title: "Models", url: "#", icon: Bot, items: [ { title: "Genesis", url: "#", }, { title: "Explorer", url: "#", }, { title: "Quantum", url: "#", }, ], }, { title: "Documentation", url: "#", icon: BookOpen, items: [ { title: "Introduction", url: "#", }, { title: "Get Started", url: "#", }, { title: "Tutorials", url: "#", }, { title: "Changelog", url: "#", }, ], }, { title: "Settings", url: "#", icon: Settings2, items: [ { title: "General", url: "#", }, { title: "Team", url: "#", }, { title: "Billing", url: "#", }, { title: "Limits", url: "#", }, ], }, ], projects: [ { name: "Design Engineering", url: "#", icon: Frame, }, { name: "Sales & Marketing", url: "#", icon: PieChart, }, { name: "Travel", url: "#", icon: MapIcon, }, ],};
function TeamSwitcher(props: { teams: { name: string; logo: LucideIcon; plan: string; }[];}) { const { isMobile } = useSidebar(); const [activeTeam, setActiveTeam] = createSignal(props.teams[0]);
return ( <Show when={activeTeam()}> {(team) => ( <SidebarMenu> <SidebarMenuItem> <DropdownMenu placement={isMobile() ? "bottom-start" : "right-start"}> <DropdownMenuTrigger as={SidebarMenuButton} size="lg" class="data-expanded:bg-sidebar-accent data-expanded:text-sidebar-accent-foreground" > <div class="flex aspect-square size-8 items-center justify-center rounded-lg bg-sidebar-primary text-sidebar-primary-foreground"> <Dynamic component={team().logo} class="size-4" /> </div> <div class="grid flex-1 text-left text-sm leading-tight"> <span class="truncate font-medium">{team().name}</span> <span class="truncate text-xs">{team().plan}</span> </div> <ChevronsUpDown class="ml-auto" /> </DropdownMenuTrigger> <DropdownMenuContent class="w-(--kb-popper-anchor-width) min-w-56 rounded-lg"> <DropdownMenuGroup> <DropdownMenuLabel class="text-muted-foreground text-xs">Teams</DropdownMenuLabel> <For each={props.teams}> {(item, index) => ( <DropdownMenuItem onClick={() => setActiveTeam(item)} class="gap-2 p-2"> <div class="flex size-6 items-center justify-center rounded-md border"> <item.logo class="size-3.5 shrink-0" /> </div> {item.name} <DropdownMenuShortcut>⌘{index() + 1}</DropdownMenuShortcut> </DropdownMenuItem> )} </For> </DropdownMenuGroup> <DropdownMenuSeparator /> <DropdownMenuGroup> <DropdownMenuItem class="gap-2 p-2"> <div class="flex size-6 items-center justify-center rounded-md border bg-transparent"> <Plus class="size-4" /> </div> <div class="font-medium text-muted-foreground">Add team</div> </DropdownMenuItem> </DropdownMenuGroup> </DropdownMenuContent> </DropdownMenu> </SidebarMenuItem> </SidebarMenu> )} </Show> );}
function NavMain(props: { items: { title: string; url: string; icon?: LucideIcon; isActive?: boolean; items?: { title: string; url: string; }[]; }[];}) { return ( <SidebarGroup> <SidebarGroupLabel>Platform</SidebarGroupLabel> <SidebarMenu> <For each={props.items}> {(item) => ( <Collapsible defaultOpen={item.isActive} class="group/collapsible"> <SidebarMenuItem> <CollapsibleTrigger as={SidebarMenuButton} tooltip={item.title}> <Show when={item.icon}>{(icon) => <Dynamic component={icon()} />}</Show> <span>{item.title}</span> <ChevronRight class="ml-auto transition-transform duration-200 group-data-[expanded]/collapsible:rotate-90" /> </CollapsibleTrigger> <CollapsibleContent> <SidebarMenuSub> <For each={item.items}> {(subItem) => ( <SidebarMenuSubItem> <SidebarMenuSubButton href={subItem.url}> <span>{subItem.title}</span> </SidebarMenuSubButton> </SidebarMenuSubItem> )} </For> </SidebarMenuSub> </CollapsibleContent> </SidebarMenuItem> </Collapsible> )} </For> </SidebarMenu> </SidebarGroup> );}
function NavProjects(props: { projects: { name: string; url: string; icon: LucideIcon; }[];}) { const { isMobile } = useSidebar();
return ( <SidebarGroup class="group-data-[collapsible=icon]:hidden"> <SidebarGroupLabel>Projects</SidebarGroupLabel> <SidebarMenu> <For each={props.projects}> {(item) => ( <SidebarMenuItem> <SidebarMenuButton as="a" href={item.url}> <item.icon /> <span>{item.name}</span> </SidebarMenuButton> <DropdownMenu placement={isMobile() ? "bottom-end" : "right-start"}> <DropdownMenuTrigger as={SidebarMenuAction} showOnHover class=""> <MoreHorizontal /> <span class="sr-only">More</span> </DropdownMenuTrigger> <DropdownMenuContent class="w-48 rounded-lg"> <DropdownMenuItem> <Folder class="text-muted-foreground" /> <span>View Project</span> </DropdownMenuItem> <DropdownMenuItem> <Forward class="text-muted-foreground" /> <span>Share Project</span> </DropdownMenuItem> <DropdownMenuSeparator /> <DropdownMenuItem> <Trash2 class="text-muted-foreground" /> <span>Delete Project</span> </DropdownMenuItem> </DropdownMenuContent> </DropdownMenu> </SidebarMenuItem> )} </For> <SidebarMenuItem> <SidebarMenuButton class="text-sidebar-foreground/70"> <MoreHorizontal class="text-sidebar-foreground/70" /> <span>More</span> </SidebarMenuButton> </SidebarMenuItem> </SidebarMenu> </SidebarGroup> );}
function NavUser(props: { user: { name: string; email: string; avatar: string; };}) { const { isMobile } = useSidebar();
return ( <SidebarMenu> <SidebarMenuItem> <DropdownMenu placement={isMobile() ? "bottom-end" : "right-end"}> <DropdownMenuTrigger as={SidebarMenuButton} size="lg" class="data-expanded:bg-sidebar-accent data-expanded:text-sidebar-accent-foreground" > <Avatar class="h-8 w-8 rounded-lg"> <AvatarImage src={props.user.avatar} alt={props.user.name} /> <AvatarFallback class="rounded-lg">CN</AvatarFallback> </Avatar> <div class="grid flex-1 text-left text-sm leading-tight"> <span class="truncate font-medium">{props.user.name}</span> <span class="truncate text-xs">{props.user.email}</span> </div> <ChevronsUpDown class="ml-auto size-4" /> </DropdownMenuTrigger> <DropdownMenuContent class="w-(--kb-popper-anchor-width) min-w-56 rounded-lg"> <DropdownMenuGroup> <DropdownMenuLabel class="p-0 font-normal"> <div class="flex items-center gap-2 px-1 py-1.5 text-left text-sm"> <Avatar class="h-8 w-8 rounded-lg"> <AvatarImage src={props.user.avatar} alt={props.user.name} /> <AvatarFallback class="rounded-lg">CN</AvatarFallback> </Avatar> <div class="grid flex-1 text-left text-sm leading-tight"> <span class="truncate font-medium">{props.user.name}</span> <span class="truncate text-xs">{props.user.email}</span> </div> </div> </DropdownMenuLabel> </DropdownMenuGroup> <DropdownMenuSeparator /> <DropdownMenuGroup> <DropdownMenuItem> <Sparkles /> Upgrade to Pro </DropdownMenuItem> </DropdownMenuGroup> <DropdownMenuSeparator /> <DropdownMenuGroup> <DropdownMenuItem> <BadgeCheck /> Account </DropdownMenuItem> <DropdownMenuItem> <CreditCard /> Billing </DropdownMenuItem> <DropdownMenuItem> <Bell /> Notifications </DropdownMenuItem> </DropdownMenuGroup> <DropdownMenuSeparator /> <DropdownMenuGroup> <DropdownMenuItem> <LogOut /> Log out </DropdownMenuItem> </DropdownMenuGroup> </DropdownMenuContent> </DropdownMenu> </SidebarMenuItem> </SidebarMenu> );}
export default function SidebarDemo(props: SidebarProps) { return ( <SidebarProvider> <Sidebar collapsible="icon" {...props}> <SidebarHeader> <TeamSwitcher teams={data.teams} /> </SidebarHeader> <SidebarContent> <NavMain items={data.navMain} /> <NavProjects projects={data.projects} /> </SidebarContent> <SidebarFooter> <NavUser user={data.user} /> </SidebarFooter> <SidebarRail /> </Sidebar> <SidebarInset> <header class="flex h-16 shrink-0 items-center gap-2 transition-[width,height] ease-linear group-has-data-[collapsible=icon]/sidebar-wrapper:h-12"> <div class="flex items-center gap-2 px-4"> <SidebarTrigger class="-ml-1" /> </div> </header> </SidebarInset> </SidebarProvider> );}Installation
Usage
import { SidebarProvider, SidebarTrigger } from "~/components/ui/sidebar";import { AppSidebar } from "~/components/app-sidebar";
export default function Layout(props: { children: JSX.Element }) { return ( <SidebarProvider> <AppSidebar /> <main> <SidebarTrigger /> {props.children} </main> </SidebarProvider> );}import { Sidebar, SidebarContent, SidebarFooter, SidebarGroup, SidebarHeader,} from "~/components/ui/sidebar";
export function AppSidebar() { return ( <Sidebar> <SidebarHeader /> <SidebarContent> <SidebarGroup /> <SidebarGroup /> </SidebarContent> <SidebarFooter /> </Sidebar> );}Composition
Use the following composition to build a Sidebar layout:
SidebarProvider├── Sidebar│ ├── SidebarHeader│ ├── SidebarContent│ │ └── SidebarGroup│ │ ├── SidebarGroupLabel│ │ ├── SidebarGroupAction│ │ ├── SidebarGroupContent│ │ └── SidebarMenu│ │ └── SidebarMenuItem│ │ ├── SidebarMenuButton│ │ ├── SidebarMenuAction│ │ ├── SidebarMenuBadge│ │ └── SidebarMenuSub│ ├── SidebarFooter│ └── SidebarRail├── SidebarInset└── SidebarTriggerStructure
- SidebarProvider manages desktop and mobile open state.
- Sidebar renders the responsive sidebar surface.
- SidebarHeader and SidebarFooter hold persistent controls.
- SidebarContent is the scrollable navigation region.
- SidebarGroup organizes related navigation.
- SidebarMenu and SidebarMenuItem build menus, actions, badges, and submenus.
- SidebarRail toggles the sidebar from its edge.
- SidebarInset wraps the primary content for the inset variant.
- SidebarTrigger toggles the sidebar from anywhere within the provider.
SidebarProvider
Wrap the application in SidebarProvider before rendering Sidebar or SidebarTrigger.
Width
For a single sidebar, change the width constants in sidebar.tsx. For multiple sidebars, set CSS variables on the provider.
<SidebarProvider style={{ "--sidebar-width": "20rem", "--sidebar-width-mobile": "20rem", }}> <Sidebar /></SidebarProvider>Keyboard Shortcut
Press ⌘B on macOS or Ctrl+B elsewhere to toggle the sidebar. The default shortcut key is b.
Sidebar
Sidebar renders the main sidebar panel.
When using the inset variant, wrap the primary content in SidebarInset.
<SidebarProvider> <Sidebar variant="inset" /> <SidebarInset> <main>{children}</main> </SidebarInset></SidebarProvider>Variants
Every visual treatment is a prop on the single Sidebar component — there are no separate variant components.
Floating
Set variant="floating" for a card-like panel with rounded corners and padding from the window edges.
import { GalleryVerticalEnd } from "lucide-solid";import type { JSX } from "solid-js";import { For, Show } from "solid-js";import { Breadcrumb, BreadcrumbItem, BreadcrumbLink, BreadcrumbList, BreadcrumbPage, BreadcrumbSeparator,} from "~/components/ui/breadcrumb";import { Separator } from "~/components/ui/separator";import { Sidebar, SidebarContent, SidebarGroup, SidebarHeader, SidebarInset, SidebarMenu, SidebarMenuButton, SidebarMenuItem, SidebarMenuSub, SidebarMenuSubButton, SidebarMenuSubItem, SidebarProvider, SidebarTrigger,} from "~/components/ui/sidebar";
// Sample dataconst data = { navMain: [ { title: "Getting Started", url: "#", items: [ { title: "Installation", url: "#" }, { title: "Project Structure", url: "#" }, ], }, { title: "Building Your Application", url: "#", items: [ { title: "Routing", url: "#" }, { title: "Data Fetching", url: "#", isActive: true }, { title: "Rendering", url: "#" }, { title: "Caching", url: "#" }, { title: "Styling", url: "#" }, { title: "Optimizing", url: "#" }, { title: "Configuring", url: "#" }, { title: "Testing", url: "#" }, { title: "Authentication", url: "#" }, { title: "Deploying", url: "#" }, { title: "Upgrading", url: "#" }, { title: "Examples", url: "#" }, ], }, { title: "API Reference", url: "#", items: [ { title: "Components", url: "#" }, { title: "File Conventions", url: "#" }, { title: "Functions", url: "#" }, { title: "next.config.js Options", url: "#" }, { title: "CLI", url: "#" }, { title: "Edge Runtime", url: "#" }, ], }, { title: "Architecture", url: "#", items: [ { title: "Accessibility", url: "#" }, { title: "Fast Refresh", url: "#" }, { title: "Next.js Compiler", url: "#" }, { title: "Supported Browsers", url: "#" }, { title: "Turbopack", url: "#" }, ], }, { title: "Community", url: "#", items: [{ title: "Contribution Guide", url: "#" }], }, ],};
export default function SidebarFloatingExample() { return ( <SidebarProvider style={ { "--sidebar-width": "19rem", } as JSX.CSSProperties } > <Sidebar variant="floating"> <SidebarHeader> <SidebarMenu> <SidebarMenuItem> <SidebarMenuButton size="lg" as="a" href="#"> <div class="flex aspect-square size-8 items-center justify-center rounded-lg bg-sidebar-primary text-sidebar-primary-foreground"> <GalleryVerticalEnd class="size-4" /> </div> <div class="flex flex-col gap-0.5 leading-none"> <span class="font-medium">Documentation</span> <span class="">v1.0.0</span> </div> </SidebarMenuButton> </SidebarMenuItem> </SidebarMenu> </SidebarHeader> <SidebarContent> <SidebarGroup> <SidebarMenu class="gap-2"> <For each={data.navMain}> {(item) => ( <SidebarMenuItem> <SidebarMenuButton as="a" href={item.url} class="font-medium"> {item.title} </SidebarMenuButton> <Show when={item.items?.length}> <SidebarMenuSub class="ml-0 border-l-0 px-1.5"> <For each={item.items}> {(subItem) => ( <SidebarMenuSubItem> <SidebarMenuSubButton as="a" href={subItem.url} isActive={subItem.isActive} > {subItem.title} </SidebarMenuSubButton> </SidebarMenuSubItem> )} </For> </SidebarMenuSub> </Show> </SidebarMenuItem> )} </For> </SidebarMenu> </SidebarGroup> </SidebarContent> </Sidebar> <SidebarInset> <header class="flex h-16 shrink-0 items-center gap-2 px-4"> <SidebarTrigger class="-ml-1" /> <Separator orientation="vertical" class="mr-2 data-[orientation=vertical]:h-4" /> <Breadcrumb> <BreadcrumbList> <BreadcrumbItem class="hidden md:block"> <BreadcrumbLink href="#">Building Your Application</BreadcrumbLink> </BreadcrumbItem> <BreadcrumbSeparator class="hidden md:block" /> <BreadcrumbItem> <BreadcrumbPage>Data Fetching</BreadcrumbPage> </BreadcrumbItem> </BreadcrumbList> </Breadcrumb> </header> <div class="flex flex-1 flex-col gap-4 p-4 pt-0"> <div class="grid auto-rows-min gap-4 md:grid-cols-3"> <div class="aspect-video rounded-xl bg-muted/50" /> <div class="aspect-video rounded-xl bg-muted/50" /> <div class="aspect-video rounded-xl bg-muted/50" /> </div> <div class="min-h-[100vh] flex-1 rounded-xl bg-muted/50 md:min-h-min" /> </div> </SidebarInset> </SidebarProvider> );}Inset
Set variant="inset" and wrap the primary content in SidebarInset.
import { For, Show } from "solid-js";import { Breadcrumb, BreadcrumbItem, BreadcrumbLink, BreadcrumbList, BreadcrumbPage, BreadcrumbSeparator,} from "~/components/ui/breadcrumb";import { Sidebar, SidebarContent, SidebarGroup, SidebarGroupContent, SidebarGroupLabel, SidebarInset, SidebarMenu, SidebarMenuButton, SidebarMenuItem, SidebarMenuSub, SidebarMenuSubButton, SidebarMenuSubItem, SidebarProvider, SidebarRail, SidebarTrigger,} from "~/components/ui/sidebar";
// Sample dataconst data = { navMain: [ { title: "Getting Started", url: "#", items: [ { title: "Installation", url: "#" }, { title: "Project Structure", url: "#" }, ], }, { title: "Building Your Application", url: "#", items: [ { title: "Routing", url: "#" }, { title: "Data Fetching", url: "#", isActive: true }, { title: "Rendering", url: "#" }, { title: "Caching", url: "#" }, { title: "Styling", url: "#" }, { title: "Optimizing", url: "#" }, { title: "Configuring", url: "#" }, { title: "Testing", url: "#" }, { title: "Authentication", url: "#" }, { title: "Deploying", url: "#" }, { title: "Upgrading", url: "#" }, { title: "Examples", url: "#" }, ], }, { title: "API Reference", url: "#", items: [ { title: "Components", url: "#" }, { title: "File Conventions", url: "#" }, { title: "Functions", url: "#" }, { title: "next.config.js Options", url: "#" }, { title: "CLI", url: "#" }, { title: "Edge Runtime", url: "#" }, ], }, { title: "Architecture", url: "#", items: [ { title: "Accessibility", url: "#" }, { title: "Fast Refresh", url: "#" }, { title: "Next.js Compiler", url: "#" }, { title: "Supported Browsers", url: "#" }, { title: "Turbopack", url: "#" }, ], }, { title: "Community", url: "#", items: [{ title: "Contribution Guide", url: "#" }], }, ],};
export default function SidebarInsetExample() { return ( <SidebarProvider> <SidebarInset> <header class="flex h-16 shrink-0 items-center gap-2 border-b px-4"> <Breadcrumb> <BreadcrumbList> <BreadcrumbItem class="hidden md:block"> <BreadcrumbLink href="#">Building Your Application</BreadcrumbLink> </BreadcrumbItem> <BreadcrumbSeparator class="hidden md:block" /> <BreadcrumbItem> <BreadcrumbPage>Data Fetching</BreadcrumbPage> </BreadcrumbItem> </BreadcrumbList> </Breadcrumb> <SidebarTrigger class="-mr-1 ml-auto rotate-180" /> </header> <div class="flex flex-1 flex-col gap-4 p-4"> <div class="grid auto-rows-min gap-4 md:grid-cols-3"> <div class="aspect-video rounded-xl bg-muted/50" /> <div class="aspect-video rounded-xl bg-muted/50" /> <div class="aspect-video rounded-xl bg-muted/50" /> </div> <div class="min-h-[100vh] flex-1 rounded-xl bg-muted/50 md:min-h-min" /> </div> </SidebarInset> <Sidebar variant="inset" side="right"> <SidebarContent> <SidebarGroup> <SidebarGroupLabel>Table of Contents</SidebarGroupLabel> <SidebarGroupContent> <SidebarMenu> <For each={data.navMain}> {(item) => ( <SidebarMenuItem> <SidebarMenuButton as="a" href={item.url} class="font-medium"> {item.title} </SidebarMenuButton> <Show when={item.items?.length}> <SidebarMenuSub> <For each={item.items}> {(subItem) => ( <SidebarMenuSubItem> <SidebarMenuSubButton as="a" href={subItem.url} isActive={subItem.isActive} > {subItem.title} </SidebarMenuSubButton> </SidebarMenuSubItem> )} </For> </SidebarMenuSub> </Show> </SidebarMenuItem> )} </For> </SidebarMenu> </SidebarGroupContent> </SidebarGroup> </SidebarContent> <SidebarRail /> </Sidebar> </SidebarProvider> );}Icon collapse
Set collapsible="icon" to collapse the sidebar to its icon rail instead of hiding it.
import { AudioWaveform, BadgeCheck, Bell, BookOpen, Bot, ChevronRight, ChevronsUpDown, Command, CreditCard, Folder, Forward, Frame, GalleryVerticalEnd, LogOut, Map as MapIcon, MoreHorizontal, PieChart, Plus, Settings2, Sparkles, SquareTerminal, Trash2,} from "lucide-solid";import type { Component, ComponentProps } from "solid-js";import { createSignal, For, Show } from "solid-js";import { Avatar, AvatarFallback, AvatarImage } from "~/components/ui/avatar";import { Breadcrumb, BreadcrumbItem, BreadcrumbLink, BreadcrumbList, BreadcrumbPage, BreadcrumbSeparator,} from "~/components/ui/breadcrumb";import { Collapsible, CollapsibleContent, CollapsibleTrigger,} from "~/components/ui/collapsible";import { DropdownMenu, DropdownMenuContent, DropdownMenuGroup, DropdownMenuItem, DropdownMenuLabel, DropdownMenuSeparator, DropdownMenuTrigger,} from "~/components/ui/dropdown-menu";import { Kbd } from "~/components/ui/kbd";import { Separator } from "~/components/ui/separator";import { Sidebar, SidebarContent, SidebarFooter, SidebarGroup, SidebarGroupLabel, SidebarHeader, SidebarInset, SidebarMenu, SidebarMenuAction, SidebarMenuButton, SidebarMenuItem, SidebarMenuSub, SidebarMenuSubButton, SidebarMenuSubItem, SidebarProvider, SidebarRail, SidebarTrigger, useSidebar,} from "~/components/ui/sidebar";
// Sample dataconst data = { user: { name: "shadcn", email: "m@example.com", avatar: "/avatars/shadcn.jpg", }, teams: [ { name: "Acme Inc", logo: GalleryVerticalEnd, plan: "Enterprise", }, { name: "Acme Corp.", logo: AudioWaveform, plan: "Startup", }, { name: "Evil Corp.", logo: Command, plan: "Free", }, ], navMain: [ { title: "Playground", url: "#", icon: SquareTerminal, isActive: true, items: [ { title: "History", url: "#" }, { title: "Starred", url: "#" }, { title: "Settings", url: "#" }, ], }, { title: "Models", url: "#", icon: Bot, items: [ { title: "Genesis", url: "#" }, { title: "Explorer", url: "#" }, { title: "Quantum", url: "#" }, ], }, { title: "Documentation", url: "#", icon: BookOpen, items: [ { title: "Introduction", url: "#" }, { title: "Get Started", url: "#" }, { title: "Tutorials", url: "#" }, { title: "Changelog", url: "#" }, ], }, { title: "Settings", url: "#", icon: Settings2, items: [ { title: "General", url: "#" }, { title: "Team", url: "#" }, { title: "Billing", url: "#" }, { title: "Limits", url: "#" }, ], }, ], projects: [ { name: "Design Engineering", url: "#", icon: Frame }, { name: "Sales & Marketing", url: "#", icon: PieChart }, { name: "Travel", url: "#", icon: MapIcon }, ],};
// Team Switcher componentfunction TeamSwitcher(props: { teams: { name: string; logo: Component<ComponentProps<"svg">>; plan: string }[];}) { const { isMobile } = useSidebar(); const [activeTeam, setActiveTeam] = createSignal(props.teams[0]);
return ( <Show when={activeTeam()} keyed> {(team) => ( <SidebarMenu> <SidebarMenuItem> <DropdownMenu placement={isMobile() ? "bottom" : "right"}> <DropdownMenuTrigger as={SidebarMenuButton} size="lg" class="data-[expanded]:bg-sidebar-accent data-[expanded]:text-sidebar-accent-foreground" > <div class="flex aspect-square size-8 items-center justify-center rounded-lg bg-sidebar-primary text-sidebar-primary-foreground"> {team.logo({ class: "size-4" })} </div> <div class="grid flex-1 text-left text-sm leading-tight"> <span class="truncate font-medium">{team.name}</span> <span class="truncate text-xs">{team.plan}</span> </div> <ChevronsUpDown class="ml-auto" /> </DropdownMenuTrigger> <DropdownMenuContent class="w-(--kb-popper-anchor-width) min-w-56 rounded-lg"> <DropdownMenuLabel class="text-muted-foreground text-xs">Teams</DropdownMenuLabel> <For each={props.teams}> {(team, index) => ( <DropdownMenuItem onSelect={() => setActiveTeam(team)} class="gap-2 p-2"> <div class="flex size-6 items-center justify-center rounded-md border"> {team.logo({ class: "size-3.5 shrink-0" })} </div> {team.name} <Kbd class="ml-auto">⌘{index() + 1}</Kbd> </DropdownMenuItem> )} </For> <DropdownMenuSeparator /> <DropdownMenuItem class="gap-2 p-2"> <div class="flex size-6 items-center justify-center rounded-md border bg-transparent"> <Plus class="size-4" /> </div> <span class="font-medium text-muted-foreground">Add team</span> </DropdownMenuItem> </DropdownMenuContent> </DropdownMenu> </SidebarMenuItem> </SidebarMenu> )} </Show> );}
// Nav Main componentfunction NavMain(props: { items: { title: string; url: string; icon?: Component<ComponentProps<"svg">>; isActive?: boolean; items?: { title: string; url: string }[]; }[];}) { return ( <SidebarGroup> <SidebarGroupLabel>Platform</SidebarGroupLabel> <SidebarMenu> <For each={props.items}> {(item) => ( <Collapsible defaultOpen={item.isActive} class="group/collapsible"> <SidebarMenuItem> <CollapsibleTrigger as={SidebarMenuButton} tooltip={item.title}> <Show when={item.icon} keyed> {(Icon) => <Icon />} </Show> <span>{item.title}</span> <ChevronRight class="ml-auto transition-transform duration-200 group-data-[expanded]/collapsible:rotate-90" /> </CollapsibleTrigger> <CollapsibleContent> <SidebarMenuSub> <For each={item.items}> {(subItem) => ( <SidebarMenuSubItem> <SidebarMenuSubButton as="a" href={subItem.url}> <span>{subItem.title}</span> </SidebarMenuSubButton> </SidebarMenuSubItem> )} </For> </SidebarMenuSub> </CollapsibleContent> </SidebarMenuItem> </Collapsible> )} </For> </SidebarMenu> </SidebarGroup> );}
// Nav Projects componentfunction NavProjects(props: { projects: { name: string; url: string; icon: Component<ComponentProps<"svg">> }[];}) { const { isMobile } = useSidebar();
return ( <SidebarGroup class="group-data-[collapsible=icon]:hidden"> <SidebarGroupLabel>Projects</SidebarGroupLabel> <SidebarMenu> <For each={props.projects}> {(item) => ( <SidebarMenuItem> <SidebarMenuButton as="a" href={item.url}> {item.icon({})} <span>{item.name}</span> </SidebarMenuButton> <DropdownMenu placement={isMobile() ? "bottom-end" : "right-start"}> <DropdownMenuTrigger as={SidebarMenuAction} showOnHover class=""> <MoreHorizontal /> <span class="sr-only">More</span> </DropdownMenuTrigger> <DropdownMenuContent class="w-48 rounded-lg"> <DropdownMenuItem> <Folder class="text-muted-foreground" /> <span>View Project</span> </DropdownMenuItem> <DropdownMenuItem> <Forward class="text-muted-foreground" /> <span>Share Project</span> </DropdownMenuItem> <DropdownMenuSeparator /> <DropdownMenuItem> <Trash2 class="text-muted-foreground" /> <span>Delete Project</span> </DropdownMenuItem> </DropdownMenuContent> </DropdownMenu> </SidebarMenuItem> )} </For> <SidebarMenuItem> <SidebarMenuButton class="text-sidebar-foreground/70"> <MoreHorizontal class="text-sidebar-foreground/70" /> <span>More</span> </SidebarMenuButton> </SidebarMenuItem> </SidebarMenu> </SidebarGroup> );}
// Nav User componentfunction NavUser(props: { user: { name: string; email: string; avatar: string } }) { const { isMobile } = useSidebar();
return ( <SidebarMenu> <SidebarMenuItem> <DropdownMenu placement={isMobile() ? "bottom-end" : "right-end"}> <DropdownMenuTrigger as={SidebarMenuButton} size="lg" class="data-[expanded]:bg-sidebar-accent data-[expanded]:text-sidebar-accent-foreground" > <Avatar class="h-8 w-8 rounded-lg"> <AvatarImage src={props.user.avatar} alt={props.user.name} /> <AvatarFallback class="rounded-lg">CN</AvatarFallback> </Avatar> <div class="grid flex-1 text-left text-sm leading-tight"> <span class="truncate font-medium">{props.user.name}</span> <span class="truncate text-xs">{props.user.email}</span> </div> <ChevronsUpDown class="ml-auto size-4" /> </DropdownMenuTrigger> <DropdownMenuContent class="w-(--kb-popper-anchor-width) min-w-56 rounded-lg"> <DropdownMenuLabel class="p-0 font-normal"> <div class="flex items-center gap-2 px-1 py-1.5 text-left text-sm"> <Avatar class="h-8 w-8 rounded-lg"> <AvatarImage src={props.user.avatar} alt={props.user.name} /> <AvatarFallback class="rounded-lg">CN</AvatarFallback> </Avatar> <div class="grid flex-1 text-left text-sm leading-tight"> <span class="truncate font-medium">{props.user.name}</span> <span class="truncate text-xs">{props.user.email}</span> </div> </div> </DropdownMenuLabel> <DropdownMenuSeparator /> <DropdownMenuGroup> <DropdownMenuItem> <Sparkles /> Upgrade to Pro </DropdownMenuItem> </DropdownMenuGroup> <DropdownMenuSeparator /> <DropdownMenuGroup> <DropdownMenuItem> <BadgeCheck /> Account </DropdownMenuItem> <DropdownMenuItem> <CreditCard /> Billing </DropdownMenuItem> <DropdownMenuItem> <Bell /> Notifications </DropdownMenuItem> </DropdownMenuGroup> <DropdownMenuSeparator /> <DropdownMenuItem> <LogOut /> Log out </DropdownMenuItem> </DropdownMenuContent> </DropdownMenu> </SidebarMenuItem> </SidebarMenu> );}
export default function SidebarIconExample() { return ( <SidebarProvider> <Sidebar collapsible="icon"> <SidebarHeader> <TeamSwitcher teams={data.teams} /> </SidebarHeader> <SidebarContent> <NavMain items={data.navMain} /> <NavProjects projects={data.projects} /> </SidebarContent> <SidebarFooter> <NavUser user={data.user} /> </SidebarFooter> <SidebarRail /> </Sidebar> <SidebarInset> <header class="flex h-16 shrink-0 items-center gap-2 transition-[width,height] ease-linear group-has-data-[collapsible=icon]/sidebar-wrapper:h-12"> <div class="flex items-center gap-2 px-4"> <SidebarTrigger class="-ml-1" /> <Separator orientation="vertical" class="mr-2 data-[orientation=vertical]:h-4" /> <Breadcrumb> <BreadcrumbList> <BreadcrumbItem class="hidden md:block"> <BreadcrumbLink href="#">Building Your Application</BreadcrumbLink> </BreadcrumbItem> <BreadcrumbSeparator class="hidden md:block" /> <BreadcrumbItem> <BreadcrumbPage>Data Fetching</BreadcrumbPage> </BreadcrumbItem> </BreadcrumbList> </Breadcrumb> </div> </header> <div class="flex flex-1 flex-col gap-4 p-4 pt-0"> <div class="grid auto-rows-min gap-4 md:grid-cols-3"> <div class="aspect-video rounded-xl bg-muted/50" /> <div class="aspect-video rounded-xl bg-muted/50" /> <div class="aspect-video rounded-xl bg-muted/50" /> </div> <div class="min-h-[100vh] flex-1 rounded-xl bg-muted/50 md:min-h-min" /> </div> </SidebarInset> </SidebarProvider> );}useSidebar
Use useSidebar inside a provider to inspect and control state.
import { useSidebar } from "~/components/ui/sidebar";
export function CustomTrigger() { const { open, toggleSidebar } = useSidebar();
return <button onClick={toggleSidebar}>{open() ? "Close" : "Open"} sidebar</button>;}SidebarHeader
Use SidebarHeader for workspace switchers, branding, search, or other persistent top-level controls.
<SidebarHeader> <SidebarMenu> <SidebarMenuItem> <SidebarMenuButton> Select workspace <ChevronDown class="ml-auto" /> </SidebarMenuButton> </SidebarMenuItem> </SidebarMenu></SidebarHeader>SidebarFooter
Use SidebarFooter for user menus, settings, or persistent actions.
<SidebarFooter> <SidebarMenu> <SidebarMenuItem> <SidebarMenuButton> <User2 /> Username </SidebarMenuButton> </SidebarMenuItem> </SidebarMenu></SidebarFooter>SidebarContent
SidebarContent is the scrollable area between the header and footer. Add one or more groups inside it.
<SidebarContent> <SidebarGroup /> <SidebarGroup /></SidebarContent>SidebarGroup
Use SidebarGroup to create a section. A group can contain a label, content, and an optional action.
<SidebarGroup> <SidebarGroupLabel>Application</SidebarGroupLabel> <SidebarGroupAction aria-label="Add project"> <Plus /> </SidebarGroupAction> <SidebarGroupContent /></SidebarGroup>Wrap a group in Collapsible when its content should be independently expandable.
<Collapsible defaultOpen class="group/collapsible"> <SidebarGroup> <CollapsibleTrigger as={SidebarGroupLabel}> Help <ChevronDown class="ml-auto group-data-[expanded]/collapsible:rotate-180" /> </CollapsibleTrigger> <CollapsibleContent> <SidebarGroupContent /> </CollapsibleContent> </SidebarGroup></Collapsible>SidebarMenu
Use SidebarMenu and SidebarMenuItem for navigation links.
<SidebarMenu> <For each={projects}> {(project) => ( <SidebarMenuItem> <SidebarMenuButton as="a" href={project.url}> <project.icon /> <span>{project.name}</span> </SidebarMenuButton> </SidebarMenuItem> )} </For></SidebarMenu>SidebarMenuButton
SidebarMenuButton renders a button by default and can render an anchor with as="a". Use isActive to mark the current item and tooltip to provide a collapsed-sidebar label.
<SidebarMenuButton as="a" href="#home" isActive tooltip="Home"> <Home /> <span>Home</span></SidebarMenuButton>SidebarMenuAction
Use SidebarMenuAction for an action associated with a menu item. Set showOnHover when the action should stay visually quiet until its item is hovered or focused.
<SidebarMenuItem> <SidebarMenuButton as="a" href="#home"> <Home /> <span>Home</span> </SidebarMenuButton> <SidebarMenuAction showOnHover aria-label="Add project"> <Plus /> </SidebarMenuAction></SidebarMenuItem>SidebarMenuSub
Use SidebarMenuSub for nested navigation inside a menu item.
<SidebarMenuItem> <SidebarMenuButton>Projects</SidebarMenuButton> <SidebarMenuSub> <SidebarMenuSubItem> <SidebarMenuSubButton href="#design-system">Design system</SidebarMenuSubButton> </SidebarMenuSubItem> </SidebarMenuSub></SidebarMenuItem>SidebarMenuBadge
Use SidebarMenuBadge to show a count or status in a menu item.
<SidebarMenuItem> <SidebarMenuButton>Inbox</SidebarMenuButton> <SidebarMenuBadge>24</SidebarMenuBadge></SidebarMenuItem>SidebarMenuSkeleton
Use SidebarMenuSkeleton while navigation data is loading.
<SidebarMenu> <For each={Array.from({ length: 5 })}> {() => ( <SidebarMenuItem> <SidebarMenuSkeleton showIcon /> </SidebarMenuItem> )} </For></SidebarMenu>SidebarTrigger
Use SidebarTrigger anywhere within the provider to toggle the sidebar.
<SidebarTrigger />SidebarRail
Use SidebarRail inside Sidebar to provide its edge toggle target.
<Sidebar> <SidebarHeader /> <SidebarContent> <SidebarGroup /> </SidebarContent> <SidebarFooter /> <SidebarRail /></Sidebar>Controlled Sidebar
Control the desktop sidebar with a Solid signal.
const [open, setOpen] = createSignal(false);
<SidebarProvider open={open()} onOpenChange={setOpen}> <Sidebar /></SidebarProvider>;Theming
The sidebar reads the sidebar semantic tokens from the active Zaidan style. Override them with your style variables when needed.
:root { --sidebar: oklch(0.985 0 0); --sidebar-foreground: oklch(0.145 0 0); --sidebar-primary: oklch(0.205 0 0); --sidebar-primary-foreground: oklch(0.985 0 0); --sidebar-accent: oklch(0.97 0 0); --sidebar-accent-foreground: oklch(0.205 0 0); --sidebar-border: oklch(0.922 0 0); --sidebar-ring: oklch(0.708 0 0);}Styling
Target the Sidebar data attributes to tailor collapsed and active states.
<Sidebar collapsible="icon"> <SidebarContent> <SidebarGroup class="group-data-[collapsible=icon]:hidden" /> </SidebarContent></Sidebar><SidebarMenuItem> <SidebarMenuButton /> <SidebarMenuAction class="peer-data-[active=true]/menu-button:opacity-100" /></SidebarMenuItem>API Reference
Sidebar's public API is native SolidJS composition. For mobile sheet primitive props, focus management, and keyboard behavior, see the Kobalte Dialog API reference.