Sidebar
A composable, themeable and customizable sidebar component.
Sidebars are one of the most complex components to build. They are central to any application and often contain a lot of moving parts.
We now have a solid foundation to build on top of. Composable. Themeable. Customizable.
Installation
npx @gentleduck/cli add sidebar
npx @gentleduck/cli add sidebar
Structure
A Sidebar component is composed of the following parts:
SidebarProvider- Handles collapsible state.Sidebar- The sidebar container.SidebarHeaderandSidebarFooter- Sticky at the top and bottom of the sidebar.SidebarContent- Scrollable content.SidebarGroup- Section within theSidebarContent.SidebarTrigger- Trigger for theSidebar.
Usage
import { SidebarProvider, SidebarTrigger } from "@/components/ui/sidebar"
import { AppSidebar } from "@/components/app-sidebar"
export default function Layout({ children }: { children: React.ReactNode }) {
return (
<SidebarProvider>
<AppSidebar />
<main>
<SidebarTrigger />
{children}
</main>
</SidebarProvider>
)
}import { SidebarProvider, SidebarTrigger } from "@/components/ui/sidebar"
import { AppSidebar } from "@/components/app-sidebar"
export default function Layout({ children }: { children: React.ReactNode }) {
return (
<SidebarProvider>
<AppSidebar />
<main>
<SidebarTrigger />
{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>
)
}import {
Sidebar,
SidebarContent,
SidebarFooter,
SidebarGroup,
SidebarHeader,
} from "@/components/ui/sidebar"
export function AppSidebar() {
return (
<Sidebar>
<SidebarHeader />
<SidebarContent>
<SidebarGroup />
<SidebarGroup />
</SidebarContent>
<SidebarFooter />
</Sidebar>
)
}SidebarProvider
The SidebarProvider component is used to provide the sidebar context to the Sidebar component. You should always wrap your application in a SidebarProvider component.
Props
| Name | Type | Description |
|---|---|---|
defaultOpen | boolean | Default open state of the sidebar. |
open | boolean | Open state of the sidebar (controlled). |
onOpenChange | (open: boolean) => void | Sets open state of the sidebar (controlled). |
Width
If you have a single sidebar in your application, you can use the SIDEBAR_WIDTH and SIDEBAR_WIDTH_MOBILE variables in sidebar.tsx to set the width of the sidebar.
const SIDEBAR_WIDTH = "16rem"
const SIDEBAR_WIDTH_MOBILE = "18rem"const SIDEBAR_WIDTH = "16rem"
const SIDEBAR_WIDTH_MOBILE = "18rem"For multiple sidebars in your application, you can use the --sidebar-width and --sidebar-width-mobile CSS variables in the style prop.
<SidebarProvider
style={
{
"--sidebar-width": "20rem",
"--sidebar-width-mobile": "20rem",
} as React.CSSProperties
}
>
<Sidebar />
</SidebarProvider><SidebarProvider
style={
{
"--sidebar-width": "20rem",
"--sidebar-width-mobile": "20rem",
} as React.CSSProperties
}
>
<Sidebar />
</SidebarProvider>Keyboard Shortcut
To trigger the sidebar, you use the cmd+b keyboard shortcut on Mac and ctrl+b on Windows.
const SIDEBAR_KEYBOARD_SHORTCUT = "b"const SIDEBAR_KEYBOARD_SHORTCUT = "b"Sidebar
The main Sidebar component used to render a collapsible sidebar.
Props
| Property | Type | Description |
|---|---|---|
side | left or right | The side of the sidebar. |
variant | sidebar, floating, or inset | The variant of the sidebar. |
collapsible | offcanvas, icon, or none | Collapsible state of the sidebar. |
dir | ltr or rtl | Text direction. Resolved by primitives useDirection (dir prop -> DirectionProvider -> 'ltr'). |
| Prop | Description |
|---|---|
offcanvas | A collapsible sidebar that slides in from the left or right. |
icon | A sidebar that collapses to icons. |
none | A non-collapsible sidebar. |
sidebar variant
Use the default sidebar variant for a standard sidebar layout.
floating variant
Use the floating variant for a sidebar with rounded borders and a drop shadow.
inset variant
Use the inset variant for a sidebar that sits inside the page layout.
If you use the inset variant, remember to wrap your main content
in a SidebarInset component.
<SidebarProvider>
<Sidebar variant="inset" />
<SidebarInset>
<main>{children}</main>
</SidebarInset>
</SidebarProvider><SidebarProvider>
<Sidebar variant="inset" />
<SidebarInset>
<main>{children}</main>
</SidebarInset>
</SidebarProvider>useSidebar
The useSidebar hook is used to control the sidebar.
import { useSidebar } from "@/components/ui/sidebar"
export function AppSidebar() {
const {
state,
open,
setOpen,
openMobile,
setOpenMobile,
isMobile,
toggleSidebar,
} = useSidebar()
}import { useSidebar } from "@/components/ui/sidebar"
export function AppSidebar() {
const {
state,
open,
setOpen,
openMobile,
setOpenMobile,
isMobile,
toggleSidebar,
} = useSidebar()
}| Property | Type | Description |
|---|---|---|
state | expanded or collapsed | The current state of the sidebar. |
open | boolean | Whether the sidebar is open. |
setOpen | (open: boolean) => void | Sets the open state of the sidebar. |
openMobile | boolean | Whether the sidebar is open on mobile. |
setOpenMobile | (open: boolean) => void | Sets the open state of the sidebar on mobile. |
isMobile | boolean | Whether the sidebar is on mobile. |
toggleSidebar | () => void | Toggles the sidebar. Desktop and mobile. |
SidebarHeader
Use the SidebarHeader component to add a sticky header to the sidebar.
<Sidebar>
<SidebarHeader>
<SidebarMenu>
<SidebarMenuItem>
<DropdownMenu>
<DropdownMenuTrigger asChild>
<SidebarMenuButton>
Select Workspace
<ChevronDown className="ml-auto" />
</SidebarMenuButton>
</DropdownMenuTrigger>
<DropdownMenuContent className="w-(--gentleduck-dropdown-menu-trigger-width)">
<DropdownMenuItem>
<span>Duck Labs</span>
</DropdownMenuItem>
</DropdownMenuContent>
</DropdownMenu>
</SidebarMenuItem>
</SidebarMenu>
</SidebarHeader>
</Sidebar><Sidebar>
<SidebarHeader>
<SidebarMenu>
<SidebarMenuItem>
<DropdownMenu>
<DropdownMenuTrigger asChild>
<SidebarMenuButton>
Select Workspace
<ChevronDown className="ml-auto" />
</SidebarMenuButton>
</DropdownMenuTrigger>
<DropdownMenuContent className="w-(--gentleduck-dropdown-menu-trigger-width)">
<DropdownMenuItem>
<span>Duck Labs</span>
</DropdownMenuItem>
</DropdownMenuContent>
</DropdownMenu>
</SidebarMenuItem>
</SidebarMenu>
</SidebarHeader>
</Sidebar>SidebarFooter
Use the SidebarFooter component to add a sticky footer to the sidebar.
<Sidebar>
<SidebarFooter>
<SidebarMenu>
<SidebarMenuItem>
<SidebarMenuButton>
<User2 /> Username
</SidebarMenuButton>
</SidebarMenuItem>
</SidebarMenu>
</SidebarFooter>
</Sidebar><Sidebar>
<SidebarFooter>
<SidebarMenu>
<SidebarMenuItem>
<SidebarMenuButton>
<User2 /> Username
</SidebarMenuButton>
</SidebarMenuItem>
</SidebarMenu>
</SidebarFooter>
</Sidebar>SidebarContent
The SidebarContent component is used to wrap the content of the sidebar. This is where you add your SidebarGroup components. It is scrollable.
Props
| Name | Type | Default | Description |
|---|---|---|---|
noScroll | boolean | true | Hides the scrollbar while keeping scroll behavior. |
<Sidebar>
<SidebarContent>
<SidebarGroup />
<SidebarGroup />
</SidebarContent>
</Sidebar><Sidebar>
<SidebarContent>
<SidebarGroup />
<SidebarGroup />
</SidebarContent>
</Sidebar>To show the scrollbar, set noScroll to false:
<SidebarContent noScroll={false}>
<SidebarGroup />
</SidebarContent><SidebarContent noScroll={false}>
<SidebarGroup />
</SidebarContent>SidebarGroup
Use the SidebarGroup component to create a section within the sidebar.
A SidebarGroup has a SidebarGroupLabel, a SidebarGroupContent and an optional SidebarGroupAction.
<SidebarGroup>
<SidebarGroupLabel>Application</SidebarGroupLabel>
<SidebarGroupAction>
<Plus /> <span className="sr-only">Add Project</span>
</SidebarGroupAction>
<SidebarGroupContent></SidebarGroupContent>
</SidebarGroup><SidebarGroup>
<SidebarGroupLabel>Application</SidebarGroupLabel>
<SidebarGroupAction>
<Plus /> <span className="sr-only">Add Project</span>
</SidebarGroupAction>
<SidebarGroupContent></SidebarGroupContent>
</SidebarGroup>To make a SidebarGroup collapsible, wrap it in a Collapsible.
<Collapsible defaultOpen className="group/collapsible">
<SidebarGroup>
<SidebarGroupLabel asChild>
<CollapsibleTrigger>
Help
<ChevronDown className="ml-auto transition-transform group-data-[open=true]/collapsible:rotate-180" />
</CollapsibleTrigger>
</SidebarGroupLabel>
<CollapsibleContent>
<SidebarGroupContent />
</CollapsibleContent>
</SidebarGroup>
</Collapsible><Collapsible defaultOpen className="group/collapsible">
<SidebarGroup>
<SidebarGroupLabel asChild>
<CollapsibleTrigger>
Help
<ChevronDown className="ml-auto transition-transform group-data-[open=true]/collapsible:rotate-180" />
</CollapsibleTrigger>
</SidebarGroupLabel>
<CollapsibleContent>
<SidebarGroupContent />
</CollapsibleContent>
</SidebarGroup>
</Collapsible>SidebarMenu
The SidebarMenu component is used for building a menu within a SidebarGroup.
<SidebarMenu>
{projects.map((project) => (
<SidebarMenuItem key={project.name}>
<SidebarMenuButton asChild>
<a href={project.url}>
<project.icon />
<span>{project.name}</span>
</a>
</SidebarMenuButton>
</SidebarMenuItem>
))}
</SidebarMenu><SidebarMenu>
{projects.map((project) => (
<SidebarMenuItem key={project.name}>
<SidebarMenuButton asChild>
<a href={project.url}>
<project.icon />
<span>{project.name}</span>
</a>
</SidebarMenuButton>
</SidebarMenuItem>
))}
</SidebarMenu>SidebarMenuButton
The SidebarMenuButton component is used to render a menu button within a SidebarMenuItem.
By default, the SidebarMenuButton renders a button but you can use the asChild prop to render a different component such as a Link or an a tag.
Props
| Name | Type | Default | Description |
|---|---|---|---|
asChild | boolean | false | Render as child element. |
isActive | boolean | false | Marks the menu item as active. |
variant | default or outline | default | The visual variant of the button. |
size | default, sm, or lg | default | The size of the button. |
tooltip | string or TooltipContent props | undefined | Tooltip shown when sidebar is collapsed. |
<SidebarMenuButton asChild isActive>
<a href="#">Home</a>
</SidebarMenuButton><SidebarMenuButton asChild isActive>
<a href="#">Home</a>
</SidebarMenuButton>Tooltip
Use the tooltip prop to show a tooltip when the sidebar is collapsed to icons.
<SidebarMenuButton tooltip="Home">
<Home />
<span>Home</span>
</SidebarMenuButton><SidebarMenuButton tooltip="Home">
<Home />
<span>Home</span>
</SidebarMenuButton>You can also pass TooltipContent props for more control:
<SidebarMenuButton
tooltip={{
children: "Home",
side: "right",
align: "center",
}}
>
<Home />
<span>Home</span>
</SidebarMenuButton><SidebarMenuButton
tooltip={{
children: "Home",
side: "right",
align: "center",
}}
>
<Home />
<span>Home</span>
</SidebarMenuButton>SidebarMenuAction
The SidebarMenuAction component is used to render a menu action within a SidebarMenuItem.
Props
| Name | Type | Default | Description |
|---|---|---|---|
asChild | boolean | false | Render as child element. |
showOnHover | boolean | false | Only show the action on hover (hidden by default). |
<SidebarMenuItem>
<SidebarMenuButton asChild>
<a href="#">
<Home />
<span>Home</span>
</a>
</SidebarMenuButton>
<SidebarMenuAction showOnHover>
<Plus /> <span className="sr-only">Add Project</span>
</SidebarMenuAction>
</SidebarMenuItem><SidebarMenuItem>
<SidebarMenuButton asChild>
<a href="#">
<Home />
<span>Home</span>
</a>
</SidebarMenuButton>
<SidebarMenuAction showOnHover>
<Plus /> <span className="sr-only">Add Project</span>
</SidebarMenuAction>
</SidebarMenuItem>SidebarMenuSub
The SidebarMenuSub component is used to render a submenu within a SidebarMenu.
Use SidebarMenuSubButton inside SidebarMenuSubItem for sub-menu items.
SidebarMenuSubButton Props
| Name | Type | Default | Description |
|---|---|---|---|
asChild | boolean | false | Render as child element. |
size | sm or md | md | The size of the sub-button. |
isActive | boolean | false | Marks the sub-item as active. |
<SidebarMenuItem>
<SidebarMenuButton />
<SidebarMenuSub>
<SidebarMenuSubItem>
<SidebarMenuSubButton isActive>
<span>Active Sub Item</span>
</SidebarMenuSubButton>
</SidebarMenuSubItem>
<SidebarMenuSubItem>
<SidebarMenuSubButton asChild>
<a href="#">Sub Item Link</a>
</SidebarMenuSubButton>
</SidebarMenuSubItem>
</SidebarMenuSub>
</SidebarMenuItem><SidebarMenuItem>
<SidebarMenuButton />
<SidebarMenuSub>
<SidebarMenuSubItem>
<SidebarMenuSubButton isActive>
<span>Active Sub Item</span>
</SidebarMenuSubButton>
</SidebarMenuSubItem>
<SidebarMenuSubItem>
<SidebarMenuSubButton asChild>
<a href="#">Sub Item Link</a>
</SidebarMenuSubButton>
</SidebarMenuSubItem>
</SidebarMenuSub>
</SidebarMenuItem>SidebarMenuBadge
The SidebarMenuBadge component is used to render a badge within a SidebarMenuItem.
<SidebarMenuItem>
<SidebarMenuButton />
<SidebarMenuBadge>24</SidebarMenuBadge>
</SidebarMenuItem><SidebarMenuItem>
<SidebarMenuButton />
<SidebarMenuBadge>24</SidebarMenuBadge>
</SidebarMenuItem>SidebarMenuSkeleton
The SidebarMenuSkeleton component is used to render a skeleton for a SidebarMenu.
Props
| Name | Type | Default | Description |
|---|---|---|---|
showIcon | boolean | false | Show a skeleton icon before the text. |
<SidebarMenu>
{Array.from({ length: 5 }).map((_, index) => (
<SidebarMenuItem key={index}>
<SidebarMenuSkeleton showIcon />
</SidebarMenuItem>
))}
</SidebarMenu><SidebarMenu>
{Array.from({ length: 5 }).map((_, index) => (
<SidebarMenuItem key={index}>
<SidebarMenuSkeleton showIcon />
</SidebarMenuItem>
))}
</SidebarMenu>SidebarTrigger
Use the SidebarTrigger component to render a button that toggles the sidebar. It extends the Button component and accepts all its props.
<SidebarTrigger /><SidebarTrigger />You can also build a custom trigger using the useSidebar hook:
import { useSidebar } from "@/components/ui/sidebar"
export function CustomTrigger() {
const { toggleSidebar } = useSidebar()
return <button onClick={toggleSidebar}>Toggle Sidebar</button>
}import { useSidebar } from "@/components/ui/sidebar"
export function CustomTrigger() {
const { toggleSidebar } = useSidebar()
return <button onClick={toggleSidebar}>Toggle Sidebar</button>
}SidebarRail
The SidebarRail component is used to render a rail within a Sidebar. This rail can be used to toggle the sidebar.
<Sidebar>
<SidebarHeader />
<SidebarContent>
<SidebarGroup />
</SidebarContent>
<SidebarFooter />
<SidebarRail />
</Sidebar><Sidebar>
<SidebarHeader />
<SidebarContent>
<SidebarGroup />
</SidebarContent>
<SidebarFooter />
<SidebarRail />
</Sidebar>Controlled Sidebar
Use the open and onOpenChange props to control the sidebar.
export function AppSidebar() {
const [open, setOpen] = React.useState(false)
return (
<SidebarProvider open={open} onOpenChange={setOpen}>
<Sidebar />
</SidebarProvider>
)
}export function AppSidebar() {
const [open, setOpen] = React.useState(false)
return (
<SidebarProvider open={open} onOpenChange={setOpen}>
<Sidebar />
</SidebarProvider>
)
}Data Attributes
All sidebar components expose data-slot attributes for precise CSS targeting.
| Component | data-slot |
|---|---|
SidebarProvider | sidebar-wrapper |
Sidebar | sidebar |
SidebarTrigger | sidebar-trigger |
SidebarRail | sidebar-rail |
SidebarInset | sidebar-inset |
SidebarInput | sidebar-input |
SidebarHeader | sidebar-header |
SidebarFooter | sidebar-footer |
SidebarSeparator | sidebar-separator |
SidebarContent | sidebar-content |
SidebarGroup | sidebar-group |
SidebarGroupLabel | sidebar-group-label |
SidebarGroupAction | sidebar-group-action |
SidebarGroupContent | sidebar-group-content |
SidebarMenu | sidebar-menu |
SidebarMenuItem | sidebar-menu-item |
SidebarMenuButton | sidebar-menu-button |
SidebarMenuAction | sidebar-menu-action |
SidebarMenuBadge | sidebar-menu-badge |
SidebarMenuSkeleton | sidebar-menu-skeleton |
SidebarMenuSub | sidebar-menu-sub |
SidebarMenuSubItem | sidebar-menu-sub-item |
SidebarMenuSubButton | sidebar-menu-sub-button |
Theming
We use the following CSS variables to theme the sidebar.
@layer base {
:root {
--sidebar-background: 0 0% 98%;
--sidebar-foreground: 240 5.3% 26.1%;
--sidebar-primary: 240 5.9% 10%;
--sidebar-primary-foreground: 0 0% 98%;
--sidebar-accent: 240 4.8% 95.9%;
--sidebar-accent-foreground: 240 5.9% 10%;
--sidebar-border: 220 13% 91%;
--sidebar-ring: 217.2 91.2% 59.8%;
}
.dark {
--sidebar-background: 240 5.9% 10%;
--sidebar-foreground: 240 4.8% 95.9%;
--sidebar-primary: 0 0% 98%;
--sidebar-primary-foreground: 240 5.9% 10%;
--sidebar-accent: 240 3.7% 15.9%;
--sidebar-accent-foreground: 240 4.8% 95.9%;
--sidebar-border: 240 3.7% 15.9%;
--sidebar-ring: 217.2 91.2% 59.8%;
}
}@layer base {
:root {
--sidebar-background: 0 0% 98%;
--sidebar-foreground: 240 5.3% 26.1%;
--sidebar-primary: 240 5.9% 10%;
--sidebar-primary-foreground: 0 0% 98%;
--sidebar-accent: 240 4.8% 95.9%;
--sidebar-accent-foreground: 240 5.9% 10%;
--sidebar-border: 220 13% 91%;
--sidebar-ring: 217.2 91.2% 59.8%;
}
.dark {
--sidebar-background: 240 5.9% 10%;
--sidebar-foreground: 240 4.8% 95.9%;
--sidebar-primary: 0 0% 98%;
--sidebar-primary-foreground: 240 5.9% 10%;
--sidebar-accent: 240 3.7% 15.9%;
--sidebar-accent-foreground: 240 4.8% 95.9%;
--sidebar-border: 240 3.7% 15.9%;
--sidebar-ring: 217.2 91.2% 59.8%;
}
}Styling
Here are some tips for styling the sidebar based on different states.
Hide a group when the sidebar collapses to icons:
<Sidebar collapsible="icon">
<SidebarContent>
<SidebarGroup className="group-data-[collapsible=icon]:hidden" />
</SidebarContent>
</Sidebar><Sidebar collapsible="icon">
<SidebarContent>
<SidebarGroup className="group-data-[collapsible=icon]:hidden" />
</SidebarContent>
</Sidebar>Show an action when the menu button is active:
<SidebarMenuItem>
<SidebarMenuButton />
<SidebarMenuAction className="peer-data-active/menu-button:opacity-100" />
</SidebarMenuItem><SidebarMenuItem>
<SidebarMenuButton />
<SidebarMenuAction className="peer-data-active/menu-button:opacity-100" />
</SidebarMenuItem>RTL Support
Set dir="rtl" on Sidebar for a local override, or set DirectionProvider once at app/root level for global direction. Trigger icon and rail positioning adjust automatically.
<Sidebar dir="rtl" side="right">
{/* ... */}
</Sidebar><Sidebar dir="rtl" side="right">
{/* ... */}
</Sidebar>