Docs
侧边栏 Sidebar

侧边栏 Sidebar

一个可组合、可主题化且可自定义的侧边栏组件

sidebar-07

A sidebar that collapses to icons.

侧边栏是最复杂的组件之一,它们是任何应用程序的核心部分,通常包含许多动态元素。

我并不喜欢构建侧边栏,所以我构建了 30 多个不同配置的侧边栏,然后,我将核心组件提取到了 sidebar.tsx 文件中。

现在,我们有了一个坚实的基础,可以在其上进行构建——可组合、可主题化、可自定义。

Browse the Blocks Library.

Installation

运行以下命令进行安装sidebar.tsx

pnpm dlx shadcn@latest add sidebar

将以下颜色添加到您的 CSS 文件中

上面的命令应该会为您安装所需的颜色如果没有,您可以复制并粘贴以下内容到您的 CSS 文件中。

稍后我们将在主题部分中详细讲解颜色的使用。

app/globals.css
@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: 224.3 76.3% 48%;
    --sidebar-primary-foreground: 0 0% 100%;
    --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%;
  }
}

结构

一个 Sidebar 组件由以下部分组成:

  • SidebarProvider - 处理可折叠状态
  • Sidebar - 侧边栏容器
  • SidebarHeaderSidebarFooter - 固定(Sticky)在侧边栏的顶部和底部
  • SidebarContent - 可滚动内容
  • SidebarGroup - SidebarContent 中的一个部分
  • SidebarTrigger - 触发 Sidebar 的按钮
Sidebar Structure

使用示例

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>
  )
}

第一个 Sidebar

让我们从最基础的侧边栏开始,一个可折叠的侧边栏,里面有菜单。

在应用的根目录添加 SidebarProviderSidebarTrigger

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.

components/app-sidebar.tsx
import { Sidebar, SidebarContent } from "@/components/ui/sidebar"
 
export function AppSidebar() {
  return (
    <Sidebar>
      <SidebarContent />
    </Sidebar>
  )
}

现在,添加一个 SidebarMenu 到侧边栏

我们将在 SidebarGroup 中使用 SidebarMenu 组件

components/app-sidebar.tsx
import { Calendar, Home, Inbox, Search, Settings } from "lucide-react"
 
import {
  Sidebar,
  SidebarContent,
  SidebarGroup,
  SidebarGroupContent,
  SidebarGroupLabel,
  SidebarMenu,
  SidebarMenuButton,
  SidebarMenuItem,
} from "@/components/ui/sidebar"
 
// Menu items.
const items = [
  {
    title: "Home",
    url: "#",
    icon: Home,
  },
  {
    title: "Inbox",
    url: "#",
    icon: Inbox,
  },
  {
    title: "Calendar",
    url: "#",
    icon: Calendar,
  },
  {
    title: "Search",
    url: "#",
    icon: Search,
  },
  {
    title: "Settings",
    url: "#",
    icon: Settings,
  },
]
 
export function AppSidebar() {
  return (
    <Sidebar>
      <SidebarContent>
        <SidebarGroup>
          <SidebarGroupLabel>Application</SidebarGroupLabel>
          <SidebarGroupContent>
            <SidebarMenu>
              {items.map((item) => (
                <SidebarMenuItem key={item.title}>
                  <SidebarMenuButton asChild>
                    <a href={item.url}>
                      <item.icon />
                      <span>{item.title}</span>
                    </a>
                  </SidebarMenuButton>
                </SidebarMenuItem>
              ))}
            </SidebarMenu>
          </SidebarGroupContent>
        </SidebarGroup>
      </SidebarContent>
    </Sidebar>
  )
}

你已经创建了你的第一个侧边栏

sidebar-demo

你的第一个侧边栏

组件

sidebar.tsx 中的组件是可组合的,即你可以通过将提供的组件组合在一起构建侧边栏,它们还可以很好地与其他 shadcn/ui 组件(如 DropdownMenu, CollapsibleDialog 等)一起使用

如果你需要修改 sidebar.tsx 中的代码,可以随意更改。代码是你的,可以以 sidebar.tsx 作为起点并根据需求进行自定义。

在接下来的部分中,我们将详细介绍每个组件及其使用方法

SidebarProvider

SidebarProvider 组件用于提供侧边栏的上下文。你应该始终在你的应用程序中将其包裹在 SidebarProvider 组件中。

Props

NameTypeDescription
defaultOpenboolean侧边栏的默认打开状态
openboolean侧边栏的打开状态(受控)
onOpenChange(open: boolean) => void设置侧边栏的打开状态(受控)

Width

如果你的应用中只有一个侧边栏,你可以在 sidebar.tsx 文件中使用 SIDEBAR_WIDTHSIDEBAR_WIDTH_MOBILE 变量来设置侧边栏的宽度。

components/ui/sidebar.tsx
const SIDEBAR_WIDTH = "16rem"
const SIDEBAR_WIDTH_MOBILE = "18rem"

如果你的应用中有多个侧边栏,你可以使用 style 属性来设置侧边栏的宽度。

你可以在 style 属性中使用 --sidebar-width--sidebar-width-mobile CSS 变量来设置侧边栏的宽度。

components/ui/sidebar.tsx
<SidebarProvider
  style={{
    "--sidebar-width": "20rem",
    "--sidebar-width-mobile": "20rem",
  }}
>
  <Sidebar />
</SidebarProvider>

这将同时处理侧边栏的宽度和布局的间距。

键盘快捷键

SIDEBAR_KEYBOARD_SHORTCUT 变量用于设置打开和关闭侧边栏的键盘快捷键。

在 Mac 上,使用 cmd+b 快捷键,Windows 上使用 ctrl+b 快捷键来触发侧边栏。

你可以通过更新 SIDEBAR_KEYBOARD_SHORTCUT 变量来修改快捷键

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

持久化状态

SidebarProvider 支持跨页面重载和服务器端渲染时持久化侧边栏状态。它使用 cookies 来存储当前的侧边栏状态。当侧边栏状态发生变化时,会设置一个默认的 cookie,名为 sidebar:state,它保存当前的开/关状态。这个 cookie 会在后续页面加载时读取,并恢复侧边栏的状态。

要在 Next.js 中持久化侧边栏状态,可以在 app/layout.tsx 中像这样设置 SidebarProvider

app/layout.tsx
import { cookies } from "next/headers"
 
import { SidebarProvider, SidebarTrigger } from "@/components/ui/sidebar"
import { AppSidebar } from "@/components/app-sidebar"
 
export async function Layout({ children }: { children: React.ReactNode }) {
  const cookieStore = await cookies()
  const defaultOpen = cookieStore.get("sidebar:state")?.value === "true"
 
  return (
    <SidebarProvider defaultOpen={defaultOpen}>
      <AppSidebar />
      <main>
        <SidebarTrigger />
        {children}
      </main>
    </SidebarProvider>
  )
}

You can change the name of the cookie by updating the SIDEBAR_COOKIE_NAME variable in sidebar.tsx.

components/ui/sidebar.tsx
const SIDEBAR_COOKIE_NAME = "sidebar:state"

The main Sidebar component used to render a collapsible sidebar.

import { Sidebar } from "@/components/ui/sidebar"
 
export function AppSidebar() {
  return <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.

side

Use the side prop to change the side of the sidebar.

Available options are left and right.

import { Sidebar } from "@/components/ui/sidebar"
 
export function AppSidebar() {
  return <Sidebar side="left | right" />
}

variant

Use the variant prop to change the variant of the sidebar.

Available options are sidebar, floating and inset.

import { Sidebar } from "@/components/ui/sidebar"
 
export function AppSidebar() {
  return <Sidebar variant="sidebar | floating | inset" />
}
<SidebarProvider>
  <Sidebar variant="inset" />
  <SidebarInset>
    <main>{children}</main>
  </SidebarInset>
</SidebarProvider>

collapsible

Use the collapsible prop to make the sidebar collapsible.

Available options are offcanvas, icon and none.

import { Sidebar } from "@/components/ui/sidebar"
 
export function AppSidebar() {
  return <Sidebar collapsible="offcanvas | icon | none" />
}
PropDescription
offcanvasA collapsible sidebar that slides in from the left or right.
iconA sidebar that collapses to icons.
noneA non-collapsible sidebar.

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()
}
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.

The following example adds a <DropdownMenu> to the SidebarHeader.

sidebar-header

A sidebar header with a dropdown menu.

components/app-sidebar.tsx
<Sidebar>
  <SidebarHeader>
    <SidebarMenu>
      <SidebarMenuItem>
        <DropdownMenu>
          <DropdownMenuTrigger asChild>
            <SidebarMenuButton>
              Select Workspace
              <ChevronDown className="ml-auto" />
            </SidebarMenuButton>
          </DropdownMenuTrigger>
          <DropdownMenuContent className="w-[--radix-popper-anchor-width]">
            <DropdownMenuItem>
              <span>Acme Inc</span>
            </DropdownMenuItem>
            <DropdownMenuItem>
              <span>Acme Corp.</span>
            </DropdownMenuItem>
          </DropdownMenuContent>
        </DropdownMenu>
      </SidebarMenuItem>
    </SidebarMenu>
  </SidebarHeader>
</Sidebar>

SidebarFooter

Use the SidebarFooter component to add a sticky footer to the sidebar.

The following example adds a <DropdownMenu> to the SidebarFooter.

sidebar-footer

A sidebar footer with a dropdown menu.

components/app-sidebar.tsx
export function AppSidebar() {
  return (
    <SidebarProvider>
      <Sidebar>
        <SidebarHeader />
        <SidebarContent />
        <SidebarFooter>
          <SidebarMenu>
            <SidebarMenuItem>
              <DropdownMenu>
                <DropdownMenuTrigger asChild>
                  <SidebarMenuButton>
                    <User2 /> Username
                    <ChevronUp className="ml-auto" />
                  </SidebarMenuButton>
                </DropdownMenuTrigger>
                <DropdownMenuContent
                  side="top"
                  className="w-[--radix-popper-anchor-width]"
                >
                  <DropdownMenuItem>
                    <span>Account</span>
                  </DropdownMenuItem>
                  <DropdownMenuItem>
                    <span>Billing</span>
                  </DropdownMenuItem>
                  <DropdownMenuItem>
                    <span>Sign out</span>
                  </DropdownMenuItem>
                </DropdownMenuContent>
              </DropdownMenu>
            </SidebarMenuItem>
          </SidebarMenu>
        </SidebarFooter>
      </Sidebar>
    </SidebarProvider>
  )
}

SidebarContent

The SidebarContent component is used to wrap the content of the sidebar. This is where you add your SidebarGroup components. It is scrollable.

import { Sidebar, SidebarContent } from "@/components/ui/sidebar"
 
export function AppSidebar() {
  return (
    <Sidebar>
      <SidebarContent>
        <SidebarGroup />
        <SidebarGroup />
      </SidebarContent>
    </Sidebar>
  )
}

SidebarGroup

Use the SidebarGroup component to create a section within the sidebar.

A SidebarGroup has a SidebarGroupLabel, a SidebarGroupContent and an optional SidebarGroupAction.

sidebar-group

A sidebar group.

import { Sidebar, SidebarContent, SidebarGroup } from "@/components/ui/sidebar"
 
export function AppSidebar() {
  return (
    <Sidebar>
      <SidebarContent>
        <SidebarGroup>
          <SidebarGroupLabel>Application</SidebarGroupLabel>
          <SidebarGroupAction>
            <Plus /> <span className="sr-only">Add Project</span>
          </SidebarGroupAction>
          <SidebarGroupContent></SidebarGroupContent>
        </SidebarGroup>
      </SidebarContent>
    </Sidebar>
  )
}

Collapsible SidebarGroup

To make a SidebarGroup collapsible, wrap it in a Collapsible.

sidebar-group-collapsible

A collapsible sidebar group.

export function AppSidebar() {
  return (
    <Collapsible defaultOpen className="group/collapsible">
      <SidebarGroup>
        <SidebarGroupLabel asChild>
          <CollapsibleTrigger>
            Help
            <ChevronDown className="ml-auto transition-transform group-data-[state=open]/collapsible:rotate-180" />
          </CollapsibleTrigger>
        </SidebarGroupLabel>
        <CollapsibleContent>
          <SidebarGroupContent />
        </CollapsibleContent>
      </SidebarGroup>
    </Collapsible>
  )
}

SidebarGroupAction

Use the SidebarGroupAction component to add an action button to the SidebarGroup.

export function AppSidebar() {
  return (
    <SidebarGroup>
      <SidebarGroupLabel asChild>Projects</SidebarGroupLabel>
      <SidebarGroupAction title="Add Project">
        <Plus /> <span className="sr-only">Add Project</span>
      </SidebarGroupAction>
      <SidebarGroupContent />
    </SidebarGroup>
  )
}
sidebar-group-action

A sidebar group with an action button.

SidebarMenu

The SidebarMenu component is used for building a menu within a SidebarGroup.

A SidebarMenu component is composed of SidebarMenuItem, SidebarMenuButton, <SidebarMenuAction /> and <SidebarMenuSub /> components.

Sidebar Menu

Here's an example of a SidebarMenu component rendering a list of projects.

sidebar-menu

A sidebar menu with a list of projects.

<Sidebar>
  <SidebarContent>
    <SidebarGroup>
      <SidebarGroupLabel>Projects</SidebarGroupLabel>
      <SidebarGroupContent>
        <SidebarMenu>
          {projects.map((project) => (
            <SidebarMenuItem key={project.name}>
              <SidebarMenuButton asChild>
                <a href={project.url}>
                  <project.icon />
                  <span>{project.name}</span>
                </a>
              </SidebarMenuButton>
            </SidebarMenuItem>
          ))}
        </SidebarMenu>
      </SidebarGroupContent>
    </SidebarGroup>
  </SidebarContent>
</Sidebar>

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.

<SidebarMenuButton asChild>
  <a href="#">Home</a>
</SidebarMenuButton>

Icon and Label

You can render an icon and a truncated label inside the button. Remember to wrap the label in a <span>.

<SidebarMenuButton asChild>
  <a href="#">
    <Home />
    <span>Home</span>
  </a>
</SidebarMenuButton>

isActive

Use the isActive prop to mark a menu item as active.

<SidebarMenuButton asChild isActive>
  <a href="#">Home</a>
</SidebarMenuButton>

SidebarMenuAction

The SidebarMenuAction component is used to render a menu action within a SidebarMenuItem.

This button works independently of the SidebarMenuButton i.e you can have the <SidebarMenuButton /> as a clickable link and the <SidebarMenuAction /> as a button.

<SidebarMenuItem>
  <SidebarMenuButton asChild>
    <a href="#">
      <Home />
      <span>Home</span>
    </a>
  </SidebarMenuButton>
  <SidebarMenuAction>
    <Plus /> <span className="sr-only">Add Project</span>
  </SidebarMenuAction>
</SidebarMenuItem>

Here's an example of a SidebarMenuAction component rendering a DropdownMenu.

sidebar-menu-action

A sidebar menu action with a dropdown menu.

<SidebarMenuItem>
  <SidebarMenuButton asChild>
    <a href="#">
      <Home />
      <span>Home</span>
    </a>
  </SidebarMenuButton>
  <DropdownMenu>
    <DropdownMenuTrigger asChild>
      <SidebarMenuAction>
        <MoreHorizontal />
      </SidebarMenuAction>
    </DropdownMenuTrigger>
    <DropdownMenuContent side="right" align="start">
      <DropdownMenuItem>
        <span>Edit Project</span>
      </DropdownMenuItem>
      <DropdownMenuItem>
        <span>Delete Project</span>
      </DropdownMenuItem>
    </DropdownMenuContent>
  </DropdownMenu>
</SidebarMenuItem>

SidebarMenuSub

The SidebarMenuSub component is used to render a submenu within a SidebarMenu.

Use <SidebarMenuSubItem /> and <SidebarMenuSubButton /> to render a submenu item.

sidebar-menu-sub

A sidebar menu with a submenu.

<SidebarMenuItem>
  <SidebarMenuButton />
  <SidebarMenuSub>
    <SidebarMenuSubItem>
      <SidebarMenuSubButton />
    </SidebarMenuSubItem>
    <SidebarMenuSubItem>
      <SidebarMenuSubButton />
    </SidebarMenuSubItem>
  </SidebarMenuSub>
</SidebarMenuItem>

Collapsible SidebarMenu

To make a SidebarMenu component collapsible, wrap it and the SidebarMenuSub components in a Collapsible.

sidebar-menu-collapsible

A collapsible sidebar menu.

<SidebarMenu>
  <Collapsible defaultOpen className="group/collapsible">
    <SidebarMenuItem>
      <CollapsibleTrigger asChild>
        <SidebarMenuButton />
      </CollapsibleTrigger>
      <CollapsibleContent>
        <SidebarMenuSub>
          <SidebarMenuSubItem />
        </SidebarMenuSub>
      </CollapsibleContent>
    </SidebarMenuItem>
  </Collapsible>
</SidebarMenu>

SidebarMenuBadge

The SidebarMenuBadge component is used to render a badge within a SidebarMenuItem.

sidebar-menu-badge

A sidebar menu with a badge.

<SidebarMenuItem>
  <SidebarMenuButton />
  <SidebarMenuBadge>24</SidebarMenuBadge>
</SidebarMenuItem>

SidebarMenuSkeleton

The SidebarMenuSkeleton component is used to render a skeleton for a SidebarMenu. You can use this to show a loading state when using React Server Components, SWR or react-query.

function NavProjectsSkeleton() {
  return (
    <SidebarMenu>
      {Array.from({ length: 5 }).map((_, index) => (
        <SidebarMenuItem key={index}>
          <SidebarMenuSkeleton />
        </SidebarMenuItem>
      ))}
    </SidebarMenu>
  )
}

SidebarSeparator

The SidebarSeparator component is used to render a separator within a Sidebar.

<Sidebar>
  <SidebarHeader />
  <SidebarSeparator />
  <SidebarContent>
    <SidebarGroup />
    <SidebarSeparator />
    <SidebarGroup />
  </SidebarContent>
</Sidebar>

SidebarTrigger

Use the SidebarTrigger component to render a button that toggles the sidebar.

The SidebarTrigger component must be used within a SidebarProvider.

<SidebarProvider>
  <Sidebar />
  <main>
    <SidebarTrigger />
  </main>
</SidebarProvider>

Custom Trigger

To create a custom trigger, you can use the useSidebar hook.

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>

Data Fetching

React Server Components

Here's an example of a SidebarMenu component rendering a list of projects using React Server Components.

sidebar-rsc

A sidebar menu using React Server Components.

Skeleton to show loading state.
function NavProjectsSkeleton() {
  return (
    <SidebarMenu>
      {Array.from({ length: 5 }).map((_, index) => (
        <SidebarMenuItem key={index}>
          <SidebarMenuSkeleton showIcon />
        </SidebarMenuItem>
      ))}
    </SidebarMenu>
  )
}
Server component fetching data.
async function NavProjects() {
  const projects = await fetchProjects()
 
  return (
    <SidebarMenu>
      {projects.map((project) => (
        <SidebarMenuItem key={project.name}>
          <SidebarMenuButton asChild>
            <a href={project.url}>
              <project.icon />
              <span>{project.name}</span>
            </a>
          </SidebarMenuButton>
        </SidebarMenuItem>
      ))}
    </SidebarMenu>
  )
}
使用示例 with React Suspense.
function AppSidebar() {
  return (
    <Sidebar>
      <SidebarContent>
        <SidebarGroup>
          <SidebarGroupLabel>Projects</SidebarGroupLabel>
          <SidebarGroupContent>
            <React.Suspense fallback={<NavProjectsSkeleton />}>
              <NavProjects />
            </React.Suspense>
          </SidebarGroupContent>
        </SidebarGroup>
      </SidebarContent>
    </Sidebar>
  )
}

SWR and React Query

You can use the same approach with SWR or react-query.

SWR
function NavProjects() {
  const { data, isLoading } = useSWR("/api/projects", fetcher)
 
  if (isLoading) {
    return (
      <SidebarMenu>
        {Array.from({ length: 5 }).map((_, index) => (
          <SidebarMenuItem key={index}>
            <SidebarMenuSkeleton showIcon />
          </SidebarMenuItem>
        ))}
      </SidebarMenu>
    )
  }
 
  if (!data) {
    return ...
  }
 
  return (
    <SidebarMenu>
      {data.map((project) => (
        <SidebarMenuItem key={project.name}>
          <SidebarMenuButton asChild>
            <a href={project.url}>
              <project.icon />
              <span>{project.name}</span>
            </a>
          </SidebarMenuButton>
        </SidebarMenuItem>
      ))}
    </SidebarMenu>
  )
}
React Query
function NavProjects() {
  const { data, isLoading } = useQuery()
 
  if (isLoading) {
    return (
      <SidebarMenu>
        {Array.from({ length: 5 }).map((_, index) => (
          <SidebarMenuItem key={index}>
            <SidebarMenuSkeleton showIcon />
          </SidebarMenuItem>
        ))}
      </SidebarMenu>
    )
  }
 
  if (!data) {
    return ...
  }
 
  return (
    <SidebarMenu>
      {data.map((project) => (
        <SidebarMenuItem key={project.name}>
          <SidebarMenuButton asChild>
            <a href={project.url}>
              <project.icon />
              <span>{project.name}</span>
            </a>
          </SidebarMenuButton>
        </SidebarMenuItem>
      ))}
    </SidebarMenu>
  )
}

Controlled Sidebar

Use the open and onOpenChange props to control the sidebar.

sidebar-controlled

A controlled sidebar.

export function AppSidebar() {
  const [open, setOpen] = React.useState(false)
 
  return (
    <SidebarProvider open={open} onOpenChange={setOpen}>
      <Sidebar />
    </SidebarProvider>
  )
}

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%;
  }
}

We intentionally use different variables for the sidebar and the rest of the application to make it easy to have a sidebar that is styled differently from the rest of the application. Think a sidebar with a darker shade from the main application.

Styling

Here are some tips for styling the sidebar based on different states.

  • Styling an element based on the sidebar collapsible state. The following will hide the SidebarGroup when the sidebar is in icon mode.
<Sidebar collapsible="icon">
  <SidebarContent>
    <SidebarGroup className="group-data-[collapsible=icon]:hidden" />
  </SidebarContent>
</Sidebar>
  • Styling a menu action based on the menu button active state. The following will force the menu action to be visible when the menu button is active.
<SidebarMenuItem>
  <SidebarMenuButton />
  <SidebarMenuAction className="peer-data-[active=true]/menu-button:opacity-100" />
</SidebarMenuItem>

You can find more tips on using states for styling in this Twitter thread.

Changelog

  • #5593 - Improved setOpen callback logic in <SidebarProvider>.

Update the setOpen callback in <SidebarProvider> as follows:

const setOpen = React.useCallback(
  (value: boolean | ((value: boolean) => boolean)) => {
    const openState = typeof value === "function" ? value(open) : value
    if (setOpenProp) {
      setOpenProp(openState)
    } else {
      _setOpen(openState)
    }
 
    // This sets the cookie to keep the sidebar state.
    document.cookie = `${SIDEBAR_COOKIE_NAME}=${openState}; path=/; max-age=${SIDEBAR_COOKIE_MAX_AGE}`
  },
  [setOpenProp, open]
)

2024-10-21 Fixed text-sidebar-foreground

  • #5491 - Moved text-sidebar-foreground from <SidebarProvider> to <Sidebar> component.

2024-10-20 Typo in useSidebar hook.

Fixed typo in useSidebar hook.

sidebar.tsx
-  throw new Error("useSidebar must be used within a Sidebar.")
+  throw new Error("useSidebar must be used within a SidebarProvider.")