106k
New

侧边栏 Sidebar

一个可组合、可主题和可定制的侧边栏组件。

sidebar-07

一个可折叠为图标的侧边栏。

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

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

现在我们有了一个坚实的基础。可组合。可主题。可定制。

浏览 Blocks 库

安装

运行以下命令安装 sidebar.tsx

pnpm dlx shadcn@latest add sidebar

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

上述命令应帮你安装颜色。如果没有,请复制下面内容粘贴到你的 CSS 文件中。

我们稍后会在主题部分详细介绍颜色。

app/globals.css
@layer base {
  :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);
  }
 
  .dark {
    --sidebar: oklch(0.205 0 0);
    --sidebar-foreground: oklch(0.985 0 0);
    --sidebar-primary: oklch(0.488 0.243 264.376);
    --sidebar-primary-foreground: oklch(0.985 0 0);
    --sidebar-accent: oklch(0.269 0 0);
    --sidebar-accent-foreground: oklch(0.985 0 0);
    --sidebar-border: oklch(1 0 0 / 10%);
    --sidebar-ring: oklch(0.439 0 0);
  }
}

结构

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

  • SidebarProvider - 处理可折叠状态。
  • Sidebar - 侧边栏容器。
  • SidebarHeaderSidebarFooter - 分别固定在侧边栏的顶部和底部。
  • SidebarContent - 可滚动内容区域。
  • SidebarGroup - 侧边栏内容内的分组区域。
  • SidebarTrigger - 侧边栏的触发按钮。
侧边栏结构

用法

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

你的第一个侧边栏

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

在应用根部添加 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"
 
// 菜单项。
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>应用</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 组件配合使用,比如 DropdownMenuCollapsibleDialog 等。

如果你需要修改 sidebar.tsx 中的代码,欢迎这么做。代码是你的。将 sidebar.tsx 作为起点,构建自己的侧边栏。

接下来的部分,我们将详细介绍各个组件及如何使用它们。

SidebarProvider

SidebarProvider 组件用于为 Sidebar 组件提供上下文。你的应用应始终被 SidebarProvider 包裹。

属性

名称类型说明
defaultOpenboolean侧边栏的默认打开状态。
openboolean侧边栏打开状态(受控)。
onOpenChange(open: boolean) => void设置侧边栏打开状态的回调(受控)。

宽度

如果你的应用中只有一个侧边栏,可以使用 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 支持在页面刷新和服务端渲染间保持侧边栏状态。它使用 cookie 来存储当前状态。每当状态改变时,会设置一个名为 sidebar_state 的默认 cookie 保存开/关状态,后续页面加载时读取此 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>
  )
}

你也可以通过修改 sidebar.tsx 中的 SIDEBAR_COOKIE_NAME 变量自定义 cookie 名称。

components/ui/sidebar.tsx
const SIDEBAR_COOKIE_NAME = "sidebar_state"

主侧边栏组件,用于渲染可折叠的侧边栏。

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

属性

属性类型说明
side'left''right'侧边栏出现的侧边。
variant'sidebar''floating''inset'侧边栏的样式变体。
collapsible'offcanvas''icon''none'侧边栏的可折叠状态。

side

side 属性来切换侧边栏所在侧。

可用选项为 leftright

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

variant

variant 属性来切换侧边栏样式变体。

可用选项为 sidebarfloatinginset

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

collapsible 属性让侧边栏可折叠。

可用选项为 offcanvasiconnone

import { Sidebar } from "@/components/ui/sidebar"
 
export function AppSidebar() {
  return <Sidebar collapsible="offcanvas | icon | none" />
}
属性说明
offcanvas可折叠的侧边栏,从左或右滑动进出。
icon折叠为图标的侧边栏。
none不可折叠的侧边栏。

useSidebar

useSidebar 钩子用于控制侧边栏状态。

import { useSidebar } from "@/components/ui/sidebar"
 
export function AppSidebar() {
  const {
    state,
    open,
    setOpen,
    openMobile,
    setOpenMobile,
    isMobile,
    toggleSidebar,
  } = useSidebar()
}
属性类型说明
state'expanded''collapsed'当前侧边栏状态。
openboolean侧边栏是否打开。
setOpen(open: boolean) => void设置侧边栏打开状态。
openMobileboolean移动端侧边栏是否打开。
setOpenMobile(open: boolean) => void设置移动端侧边栏打开状态。
isMobileboolean是否处于移动端状态。
toggleSidebar() => void切换侧边栏开关。支持桌面和移动端。

SidebarHeader

SidebarHeader 组件用于为侧边栏添加一个粘性头部。

下面示例展示在 SidebarHeader 中添加一个 <DropdownMenu>

sidebar-header

带下拉菜单的侧边栏头部。

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

使用 SidebarFooter 组件添加固定底部区域。

下面示例在 SidebarFooter 添加一个 <DropdownMenu>

sidebar-footer

带下拉菜单的侧边栏底部。

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

SidebarContent 包裹侧边栏内容,是添加 SidebarGroup 的地方。它是可滚动的。

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

SidebarGroup

使用 SidebarGroup 创建侧边栏的分区。

SidebarGroup 包含 SidebarGroupLabelSidebarGroupContent 和可选的 SidebarGroupAction

sidebar-group

一个侧边栏分组。

import { Sidebar, SidebarContent, SidebarGroup } from "@/components/ui/sidebar"
 
export function AppSidebar() {
  return (
    <Sidebar>
      <SidebarContent>
        <SidebarGroup>
          <SidebarGroupLabel>应用</SidebarGroupLabel>
          <SidebarGroupAction>
            <Plus /> <span className="sr-only">添加项目</span>
          </SidebarGroupAction>
          <SidebarGroupContent></SidebarGroupContent>
        </SidebarGroup>
      </SidebarContent>
    </Sidebar>
  )
}

可折叠的 SidebarGroup

若想让 SidebarGroup 可折叠,用 Collapsible 包裹它。

sidebar-group-collapsible

一个可折叠的侧边栏分组。

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

SidebarGroupAction

使用 SidebarGroupActionSidebarGroup 添加操作按钮。

sidebar-group-action

带操作按钮的侧边栏分组。

export function AppSidebar() {
  return (
    <SidebarGroup>
      <SidebarGroupLabel asChild>项目</SidebarGroupLabel>
      <SidebarGroupAction title="添加项目">
        <Plus /> <span className="sr-only">添加项目</span>
      </SidebarGroupAction>
      <SidebarGroupContent />
    </SidebarGroup>
  )
}

SidebarMenu

SidebarMenu 用于在 SidebarGroup 内创建菜单。

SidebarMenuSidebarMenuItemSidebarMenuButton<SidebarMenuAction /><SidebarMenuSub /> 组成。

侧边栏菜单

下面示例是一个渲染项目列表的侧边栏菜单。

sidebar-menu

带项目列表的侧边栏菜单。

<Sidebar>
  <SidebarContent>
    <SidebarGroup>
      <SidebarGroupLabel>项目</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

SidebarMenuButton 用于在 SidebarMenuItem 中渲染菜单按钮。

链接或锚点

默认渲染为按钮,但可用 asChild 属性渲染其他组件,如 Linka 标签。

<SidebarMenuButton asChild>
  <a href="#">首页</a>
</SidebarMenuButton>

图标与标签

可在按钮中渲染图标和被截断的标签。标签应包裹在 <span> 中。

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

isActive

使用 isActive 属性标记菜单项为激活状态。

<SidebarMenuButton asChild isActive>
  <a href="#">首页</a>
</SidebarMenuButton>

SidebarMenuAction

SidebarMenuAction 用于在 SidebarMenuItem 中渲染菜单操作按钮。

此按钮独立于 SidebarMenuButton,即你可以有一个 <SidebarMenuButton /> 做链接点击,另一个 <SidebarMenuAction /> 作为按钮。

<SidebarMenuItem>
  <SidebarMenuButton asChild>
    <a href="#">
      <Home />
      <span>首页</span>
    </a>
  </SidebarMenuButton>
  <SidebarMenuAction>
    <Plus /> <span className="sr-only">添加项目</span>
  </SidebarMenuAction>
</SidebarMenuItem>

SidebarMenuAction 里嵌套 DropdownMenu 示例。

sidebar-menu-action

带下拉菜单的侧边栏菜单操作。

<SidebarMenuItem>
  <SidebarMenuButton asChild>
    <a href="#">
      <Home />
      <span>首页</span>
    </a>
  </SidebarMenuButton>
  <DropdownMenu>
    <DropdownMenuTrigger asChild>
      <SidebarMenuAction>
        <MoreHorizontal />
      </SidebarMenuAction>
    </DropdownMenuTrigger>
    <DropdownMenuContent side="right" align="start">
      <DropdownMenuItem>
        <span>编辑项目</span>
      </DropdownMenuItem>
      <DropdownMenuItem>
        <span>删除项目</span>
      </DropdownMenuItem>
    </DropdownMenuContent>
  </DropdownMenu>
</SidebarMenuItem>

SidebarMenuSub

SidebarMenuSub 用于渲染子菜单。

<SidebarMenuSubItem /><SidebarMenuSubButton /> 渲染子菜单项。

sidebar-menu-sub

带子菜单的侧边栏菜单。

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

可折叠的 SidebarMenu

要让 SidebarMenu 可折叠,使用 Collapsible 包裹它和 SidebarMenuSub

sidebar-menu-collapsible

一个可折叠的侧边栏菜单。

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

SidebarMenuBadge

SidebarMenuBadge 用于在 SidebarMenuItem 中渲染徽章。

sidebar-menu-badge

带徽章的侧边栏菜单。

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

SidebarMenuSkeleton

SidebarMenuSkeleton 用于渲染菜单的骨架屏。可以用作 React 服务端组件、SWR 或 react-query 加载时的占位。

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

SidebarSeparator

SidebarSeparator 用于在侧边栏内渲染分割线。

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

SidebarTrigger

SidebarTrigger 用于渲染切换侧边栏的按钮。

必须在 SidebarProvider 内使用。

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

自定义触发器

使用 useSidebar 钩子创建自定义触发器。

import { useSidebar } from "@/components/ui/sidebar"
 
export function CustomTrigger() {
  const { toggleSidebar } = useSidebar()
 
  return <button onClick={toggleSidebar}>切换侧边栏</button>
}

SidebarRail

SidebarRail 用于在侧边栏内渲染一个轨道,可用于切换侧边栏。

<Sidebar>
  <SidebarHeader />
  <SidebarContent>
    <SidebarGroup />
  </SidebarContent>
  <SidebarFooter />
  <SidebarRail />
</Sidebar>

数据获取

React 服务端组件

下面是一个使用 React 服务端组件渲染项目列表的 SidebarMenu 例子。

sidebar-rsc

使用 React 服务端组件的侧边栏菜单。

骨架屏显示加载状态。
function NavProjectsSkeleton() {
  return (
    <SidebarMenu>
      {Array.from({ length: 5 }).map((_, index) => (
        <SidebarMenuItem key={index}>
          <SidebarMenuSkeleton showIcon />
        </SidebarMenuItem>
      ))}
    </SidebarMenu>
  )
}
服务端组件获取数据。
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>
  )
}
配合 React Suspense 使用。
function AppSidebar() {
  return (
    <Sidebar>
      <SidebarContent>
        <SidebarGroup>
          <SidebarGroupLabel>项目</SidebarGroupLabel>
          <SidebarGroupContent>
            <React.Suspense fallback={<NavProjectsSkeleton />}>
              <NavProjects />
            </React.Suspense>
          </SidebarGroupContent>
        </SidebarGroup>
      </SidebarContent>
    </Sidebar>
  )
}

SWR 与 React Query

也可以用同样的方式配合 SWRreact-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>
  )
}

受控侧边栏

使用 openonOpenChange 属性来控制侧边栏。

sidebar-controlled

一个受控的侧边栏。

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

主题

我们使用以下 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: 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%;
  }
}

我们故意对侧边栏和应用其他部分使用不同的变量,这样可以让侧边栏拥有与主应用不同的样式。例如,给侧边栏一个比主应用更暗的色调。

样式

以下是根据不同状态样式化侧边栏的小技巧。

  • 根据侧边栏折叠状态样式化元素。 以下示例会在侧边栏为 icon 模式时隐藏 SidebarGroup
<Sidebar collapsible="icon">
  <SidebarContent>
    <SidebarGroup className="group-data-[collapsible=icon]:hidden" />
  </SidebarContent>
</Sidebar>
  • 根据菜单按钮激活状态样式化菜单操作。 以下会在菜单按钮处于激活状态时强制显示菜单操作。
<SidebarMenuItem>
  <SidebarMenuButton />
  <SidebarMenuAction className="peer-data-[active=true]/menu-button:opacity-100" />
</SidebarMenuItem>

更多关于使用状态进行样式化的提示见此Twitter 线程

更新日志

  • #5593 - 改进 <SidebarProvider> 中 setOpen 回调逻辑。

更新 <SidebarProvider> 中的 setOpen 回调:

const setOpen = React.useCallback(
  (value: boolean | ((value: boolean) => boolean)) => {
    const openState = typeof value === "function" ? value(open) : value
    if (setOpenProp) {
      setOpenProp(openState)
    } else {
      _setOpen(openState)
    }
 
    // 设置 cookie 保存侧边栏状态。
    document.cookie = `${SIDEBAR_COOKIE_NAME}=${openState}; path=/; max-age=${SIDEBAR_COOKIE_MAX_AGE}`
  },
  [setOpenProp, open]
)

2024-10-21 修复 text-sidebar-foreground

  • #5491 - 将 text-sidebar-foreground<SidebarProvider> 移动到 <Sidebar>

2024-10-20 修正 useSidebar 钩子中的拼写错误。

修正拼写错误。

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