Skip to main content

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.

Browse the Blocks Library.

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.
  • SidebarHeader and SidebarFooter - Sticky at the top and bottom of the sidebar.
  • SidebarContent - Scrollable content.
  • SidebarGroup - Section within the SidebarContent.
  • SidebarTrigger - Trigger for the Sidebar.

Usage

app/layout.tsx
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>
  )
}
app/layout.tsx
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>
  )
}
components/app-sidebar.tsx
import {
  Sidebar,
  SidebarContent,
  SidebarFooter,
  SidebarGroup,
  SidebarHeader,
} from "@/components/ui/sidebar"
 
export function AppSidebar() {
  return (
    <Sidebar>
      <SidebarHeader />
      <SidebarContent>
        <SidebarGroup />
        <SidebarGroup />
      </SidebarContent>
      <SidebarFooter />
    </Sidebar>
  )
}
components/app-sidebar.tsx
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

NameTypeDescription
defaultOpenbooleanDefault open state of the sidebar.
openbooleanOpen state of the sidebar (controlled).
onOpenChange(open: boolean) => voidSets 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.

components/ui/sidebar.tsx
const SIDEBAR_WIDTH = "16rem"
const SIDEBAR_WIDTH_MOBILE = "18rem"
components/ui/sidebar.tsx
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.

components/ui/sidebar.tsx
const SIDEBAR_KEYBOARD_SHORTCUT = "b"
components/ui/sidebar.tsx
const SIDEBAR_KEYBOARD_SHORTCUT = "b"

The main Sidebar component used to render a collapsible sidebar.

Props

PropertyTypeDescription
sideleft or rightThe side of the sidebar.
variantsidebar, floating, or insetThe variant of the sidebar.
collapsibleoffcanvas, icon, or noneCollapsible state of the sidebar.
dirltr or rtlText direction. Resolved by primitives useDirection (dir prop -> DirectionProvider -> 'ltr').
PropDescription
offcanvasA collapsible sidebar that slides in from the left or right.
iconA sidebar that collapses to icons.
noneA non-collapsible sidebar.

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.

<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()
}
PropertyTypeDescription
stateexpanded or collapsedThe current state of the sidebar.
openbooleanWhether the sidebar is open.
setOpen(open: boolean) => voidSets the open state of the sidebar.
openMobilebooleanWhether the sidebar is open on mobile.
setOpenMobile(open: boolean) => voidSets the open state of the sidebar on mobile.
isMobilebooleanWhether the sidebar is on mobile.
toggleSidebar() => voidToggles the sidebar. Desktop and mobile.

SidebarHeader

Use the SidebarHeader component to add a sticky header to the sidebar.

components/app-sidebar.tsx
<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>
components/app-sidebar.tsx
<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

NameTypeDefaultDescription
noScrollbooleantrueHides 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

NameTypeDefaultDescription
asChildbooleanfalseRender as child element.
isActivebooleanfalseMarks the menu item as active.
variantdefault or outlinedefaultThe visual variant of the button.
sizedefault, sm, or lgdefaultThe size of the button.
tooltipstring or TooltipContent propsundefinedTooltip 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

NameTypeDefaultDescription
asChildbooleanfalseRender as child element.
showOnHoverbooleanfalseOnly 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

NameTypeDefaultDescription
asChildbooleanfalseRender as child element.
sizesm or mdmdThe size of the sub-button.
isActivebooleanfalseMarks 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

NameTypeDefaultDescription
showIconbooleanfalseShow 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.

Componentdata-slot
SidebarProvidersidebar-wrapper
Sidebarsidebar
SidebarTriggersidebar-trigger
SidebarRailsidebar-rail
SidebarInsetsidebar-inset
SidebarInputsidebar-input
SidebarHeadersidebar-header
SidebarFootersidebar-footer
SidebarSeparatorsidebar-separator
SidebarContentsidebar-content
SidebarGroupsidebar-group
SidebarGroupLabelsidebar-group-label
SidebarGroupActionsidebar-group-action
SidebarGroupContentsidebar-group-content
SidebarMenusidebar-menu
SidebarMenuItemsidebar-menu-item
SidebarMenuButtonsidebar-menu-button
SidebarMenuActionsidebar-menu-action
SidebarMenuBadgesidebar-menu-badge
SidebarMenuSkeletonsidebar-menu-skeleton
SidebarMenuSubsidebar-menu-sub
SidebarMenuSubItemsidebar-menu-sub-item
SidebarMenuSubButtonsidebar-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>