切换语言
切换主题

用 shadcn/ui 搭建后台骨架:Sidebar + Layout 最佳实践

手把手教你搭建一个可扩展的后台管理系统骨架,从 Sidebar 组件到 Next.js Layout 整合,完整代码可直接上手。


上周接了个后台管理系统的项目,第一时间想到 shadcn/ui。说实话,之前用过 Ant Design 和 MUI,总觉得样式定制起来挺费劲的——要么得覆盖一大堆样式,要么被框架的设计理念绑死。

shadcn/ui 不一样。它是 “Copy-paste” 模式,代码直接放你项目里,想改就改。用了两周下来,感觉确实香。特别是 Sidebar 组件,配合 Next.js 的 App Router,搭建后台骨架清爽多了。

这篇文章就把我的实践过程整理出来。从零开始,带你搭建一个可扩展的后台布局。


一、为什么选择 shadcn/ui Sidebar?

先说说我踩过的坑。

之前用 Ant Design Pro,开箱即用确实爽,但项目做久了就开始头疼:想改侧边栏样式,得翻半天文档;想做点个性化交互,发现框架限制太多了。MUI 也有类似的问题,主题定制虽说灵活,但那是建立在你会写 Material Design 的前提下。

传统方案的痛点

Ant Design:功能全面,但定制成本高。想改个侧边栏的圆角,可能要写三层样式覆盖。

MUI:设计体系完整,但学习曲线陡。Styled Components 的写法,团队新人上手得一周。

自己写:完全可控,但从零写一个支持响应式、可访问性、键盘导航的侧边栏?少说得三天。

shadcn/ui 的解法

shadcn/ui 走的是另一条路:

  • Copy-paste 模式:组件代码直接放你项目里,没有黑盒依赖
  • Radix UI 基础:可访问性内置,键盘导航、ARIA 属性都帮你处理好
  • Tailwind CSS 驱动:样式就是类名,想改就改,没有样式覆盖的烦恼

我见过不少团队从 MUI 迁移到 shadcn/ui,原因很简单:他们想要的是可控,而不是开箱即用的模板

适用场景

如果你正在做:

  • 中小型后台管理系统
  • SaaS 产品的控制台
  • 内部工具或运营平台

shadcn/ui Sidebar 值得一试。它不会给你一个完整的后台模板,但会给你一个足够灵活的骨架。


二、Sidebar 组件架构解析

在动手之前,先理解 shadcn/ui Sidebar 的组件体系。这块是官方文档里写得比较清楚的部分,我这里快速过一遍。

核心组件清单

Sidebar 由一整套组件组成,各司其职:

SidebarProvider   // 状态上下文,包裹整个应用
Sidebar          // 侧边栏容器
SidebarHeader    // 顶部固定区域,放 Logo
SidebarContent   // 可滚动内容区,放菜单
SidebarGroup     // 菜单分组
SidebarMenu      // 菜单列表
SidebarMenuItem  // 菜单项
SidebarMenuButton // 菜单按钮(支持 Link)
SidebarFooter    // 底部固定区域,放用户信息
SidebarTrigger   // 折叠/展开按钮
SidebarInset     // 主内容区包装器

看着组件多,其实关系很清晰:

SidebarProvider
├── Sidebar
│   ├── SidebarHeader
│   ├── SidebarContent
│   │   └── SidebarGroup
│   │       └── SidebarMenu
│   │           └── SidebarMenuItem
│   │               └── SidebarMenuButton
│   └── SidebarFooter
└── SidebarInset
    └── {children}

状态管理机制

Sidebar 的折叠状态由 SidebarProvider 管理。有两种模式:

非受控模式(推荐):

<SidebarProvider defaultOpen={true}>
  <Sidebar />
</SidebarProvider>

受控模式

const [open, setOpen] = useState(true);

<SidebarProvider open={open} onOpenChange={setOpen}>
  <Sidebar />
</SidebarProvider>

大多数情况下,非受控模式就够了。除非你需要在其他地方控制 Sidebar 状态(比如用户设置里有个开关),才用受控模式。

响应式设计原理

Sidebar 内置了响应式支持:

  • 桌面端:侧边栏固定在左侧,可以通过 SidebarTrigger 折叠
  • 移动端:自动变成抽屉式(Sheet),点击 Trigger 弹出

这个逻辑是组件内部处理的,你只需要在 SidebarProvider 里配置好,剩下的交给组件。


三、Next.js Layout 整合实战

好,核心概念讲完了。接下来是重头戏——把 Sidebar 整合到 Next.js 的 Layout 系统里。

3.1 项目结构设计

我推荐用 Next.js 的 Route Groups 组织布局。这样可以让不同功能的页面有不同的布局,而不影响 URL 结构。

app/
├── layout.tsx              # Root Layout(全局)
├── (marketing)/            # 营销页面组(Landing、About)
│   ├── layout.tsx          # 无 Sidebar
│   └── page.tsx            # 首页
├── (dashboard)/            # 后台页面组
│   ├── layout.tsx          # 带 Sidebar 的布局
│   ├── page.tsx            # Dashboard 主页
│   ├── users/
│   │   └── page.tsx        # 用户管理
│   └── settings/
│       └── page.tsx        # 系统设置
└── (auth)/                 # 认证页面组
    ├── layout.tsx          # 居中布局
    ├── login/
    │   └── page.tsx        # 登录页
    └── register/
        └── page.tsx        # 注册页

这个结构的好处是:

  1. 布局隔离:营销页面不需要 Sidebar,后台页面需要,用 Route Groups 自然分开
  2. URL 简洁(dashboard) 不出现在 URL 里,/users 就是 /users
  3. 易于扩展:新增页面组只需要新建文件夹

3.2 Root Layout 配置

Root Layout 是整个应用的入口,我们在这里配置全局的东西:主题、字体、SidebarProvider。

// app/layout.tsx
import type { Metadata } from "next";
import { Inter } from "next/font/google";
import { SidebarProvider } from "@/components/ui/sidebar";
import "./globals.css";

const inter = Inter({ subsets: ["latin"] });

export const metadata: Metadata = {
  title: "Admin Dashboard",
  description: "Built with shadcn/ui and Next.js",
};

export default function RootLayout({
  children,
}: {
  children: React.ReactNode;
}) {
  return (
    &lt;html lang="zh-CN"&gt;
      &lt;body className={inter.className}&gt;
        &lt;SidebarProvider&gt;
          {children}
        &lt;/SidebarProvider&gt;
      &lt;/body&gt;
    &lt;/html&gt;
  );
}

注意这里 SidebarProvider 放在 Root Layout,而不是 Dashboard Layout。这样做的好处是 Sidebar 状态可以跨页面保持(比如从 /users 跳到 /settings,折叠状态不会丢失)。

3.3 Dashboard Layout 实现

Dashboard Layout 是后台的核心布局,Sidebar 在这里引入。

// app/(dashboard)/layout.tsx
import { AppSidebar } from "@/components/app-sidebar";
import { SidebarInset, SidebarTrigger } from "@/components/ui/sidebar";
import { Separator } from "@/components/ui/separator";
import {
  Breadcrumb,
  BreadcrumbItem,
  BreadcrumbLink,
  BreadcrumbList,
  BreadcrumbPage,
  BreadcrumbSeparator,
} from "@/components/ui/breadcrumb";

export default function DashboardLayout({
  children,
}: {
  children: React.ReactNode;
}) {
  return (
    &lt;&gt;
      &lt;AppSidebar /&gt;
      &lt;SidebarInset&gt;
        &lt;header className="flex h-16 shrink-0 items-center gap-2 border-b px-4"&gt;
          &lt;SidebarTrigger className="-ml-1" /&gt;
          &lt;Separator orientation="vertical" className="mr-2 h-4" /&gt;
          &lt;Breadcrumb&gt;
            &lt;BreadcrumbList&gt;
              &lt;BreadcrumbItem className="hidden md:block"&gt;
                &lt;BreadcrumbLink href="/dashboard"&gt;
                  后台管理
                &lt;/BreadcrumbLink&gt;
              &lt;/BreadcrumbItem&gt;
              &lt;BreadcrumbSeparator className="hidden md:block" /&gt;
              &lt;BreadcrumbItem&gt;
                &lt;BreadcrumbPage&gt;概览&lt;/BreadcrumbPage&gt;
              &lt;/BreadcrumbItem&gt;
            &lt;/BreadcrumbList&gt;
          &lt;/Breadcrumb&gt;
        &lt;/header&gt;
        &lt;main className="flex-1 p-4 pt-6"&gt;{children}&lt;/main&gt;
      &lt;/SidebarInset&gt;
    &lt;/&gt;
  );
}

这个布局包含:

  1. AppSidebar:自定义的侧边栏组件(下一节实现)
  2. SidebarInset:主内容区包装器,自动处理 Sidebar 折叠时的宽度
  3. Header:顶部导航栏,包含 SidebarTrigger 和面包屑
  4. Main:主内容区

3.4 AppSidebar 组件实现

现在来实现侧边栏本身。我推荐用配置驱动的方式:导航菜单存在一个配置文件里,组件根据配置渲染。

先定义导航配置:

// lib/navigation.ts
import {
  Home,
  Users,
  Settings,
  FileText,
  BarChart3,
  Shield,
} from "lucide-react";

export interface NavItem {
  title: string;
  href: string;
  icon: React.ComponentType&lt;{ className?: string }&gt;;
  badge?: string;
}

export const navConfig: NavItem[] = [
  {
    title: "概览",
    href: "/dashboard",
    icon: Home,
  },
  {
    title: "用户管理",
    href: "/users",
    icon: Users,
    badge: "12", // 角标
  },
  {
    title: "数据分析",
    href: "/analytics",
    icon: BarChart3,
  },
  {
    title: "内容管理",
    href: "/content",
    icon: FileText,
  },
  {
    title: "系统设置",
    href: "/settings",
    icon: Settings,
  },
  {
    title: "权限管理",
    href: "/permissions",
    icon: Shield,
  },
];

然后实现 AppSidebar:

// components/app-sidebar.tsx
"use client";

import Link from "next/link";
import { usePathname } from "next/navigation";
import {
  Sidebar,
  SidebarContent,
  SidebarFooter,
  SidebarGroup,
  SidebarGroupContent,
  SidebarGroupLabel,
  SidebarHeader,
  SidebarMenu,
  SidebarMenuButton,
  SidebarMenuItem,
} from "@/components/ui/sidebar";
import { navConfig } from "@/lib/navigation";
import { Logo } from "@/components/logo";
import { UserNav } from "@/components/user-nav";

export function AppSidebar() {
  const pathname = usePathname();

  return (
    &lt;Sidebar&gt;
      &lt;SidebarHeader className="border-b border-border"&gt;
        &lt;Logo /&gt;
      &lt;/SidebarHeader&gt;

      &lt;SidebarContent&gt;
        &lt;SidebarGroup&gt;
          &lt;SidebarGroupLabel&gt;导航菜单&lt;/SidebarGroupLabel&gt;
          &lt;SidebarGroupContent&gt;
            &lt;SidebarMenu&gt;
              {navConfig.map((item) =&gt; {
                const isActive = pathname === item.href;

                return (
                  &lt;SidebarMenuItem key={item.href}&gt;
                    &lt;SidebarMenuButton
                      asChild
                      isActive={isActive}
                      tooltip={item.title}
                    &gt;
                      &lt;Link href={item.href}&gt;
                        &lt;item.icon className="h-4 w-4" /&gt;
                        &lt;span&gt;{item.title}&lt;/span&gt;
                        {item.badge && (
                          &lt;span className="ml-auto text-xs bg-primary text-primary-foreground rounded-full px-2 py-0.5"&gt;
                            {item.badge}
                          &lt;/span&gt;
                        )}
                      &lt;/Link&gt;
                    &lt;/SidebarMenuButton&gt;
                  &lt;/SidebarMenuItem&gt;
                );
              })}
            &lt;/SidebarMenu&gt;
          &lt;/SidebarGroupContent&gt;
        &lt;/SidebarGroup&gt;
      &lt;/SidebarContent&gt;

      &lt;SidebarFooter className="border-t border-border"&gt;
        &lt;UserNav /&gt;
      &lt;/SidebarFooter&gt;
    &lt;/Sidebar&gt;
  );
}

这里有个关键点:路由高亮。我用 usePathname() 获取当前路径,然后跟 item.href 比较,匹配上就给 SidebarMenuButtonisActive={true},组件会自动应用激活样式。

3.5 多级菜单实现

如果你的后台有二级菜单,可以用 Collapsible 包裹 SidebarGroup

import {
  Collapsible,
  CollapsibleContent,
  CollapsibleTrigger,
} from "@/components/ui/collapsible";
import { ChevronDown } from "lucide-react";

// 在 SidebarMenu 里
&lt;Collapsible defaultOpen&gt;
  &lt;SidebarMenuItem&gt;
    &lt;CollapsibleTrigger asChild&gt;
      &lt;SidebarMenuButton&gt;
        &lt;Settings className="h-4 w-4" /&gt;
        &lt;span&gt;系统设置&lt;/span&gt;
        &lt;ChevronDown className="ml-auto h-4 w-4 transition-transform group-data-[state=open]/collapsible:rotate-180" /&gt;
      &lt;/SidebarMenuButton&gt;
    &lt;/CollapsibleTrigger&gt;
    &lt;CollapsibleContent&gt;
      &lt;SidebarMenuSub&gt;
        &lt;SidebarMenuSubItem&gt;
          &lt;SidebarMenuSubButton href="/settings/general"&gt;
            &lt;span&gt;基础设置&lt;/span&gt;
          &lt;/SidebarMenuSubButton&gt;
        &lt;/SidebarMenuSubItem&gt;
        &lt;SidebarMenuSubItem&gt;
          &lt;SidebarMenuSubButton href="/settings/security"&gt;
            &lt;span&gt;安全设置&lt;/span&gt;
          &lt;/SidebarMenuSubButton&gt;
        &lt;/SidebarMenuSubItem&gt;
      &lt;/SidebarMenuSub&gt;
    &lt;/CollapsibleContent&gt;
  &lt;/SidebarMenuItem&gt;
&lt;/Collapsible&gt;

四、进阶功能实现

基础布局搭好了,接下来看点实用的进阶功能。

4.1 权限控制(RBAC)

很多后台需要根据用户角色显示不同的菜单。实现方式很简单:在导航配置里加个 roles 字段,渲染时过滤。

先改造配置:

// lib/navigation.ts
export interface NavItem {
  title: string;
  href: string;
  icon: React.ComponentType&lt;{ className?: string }&gt;;
  roles?: string[]; // 允许访问的角色
}

export const navConfig: NavItem[] = [
  {
    title: "概览",
    href: "/dashboard",
    icon: Home,
    // 不设 roles,所有人都能看
  },
  {
    title: "用户管理",
    href: "/users",
    icon: Users,
    roles: ["admin", "manager"], // 只有 admin 和 manager 能看
  },
  {
    title: "权限管理",
    href: "/permissions",
    icon: Shield,
    roles: ["admin"], // 只有 admin 能看
  },
];

然后在 AppSidebar 里根据用户角色过滤:

// components/app-sidebar.tsx
import { useAuth } from "@/hooks/use-auth"; // 假设你有个 auth hook

export function AppSidebar() {
  const pathname = usePathname();
  const { user } = useAuth(); // 获取当前用户

  const filteredNav = navConfig.filter((item) =&gt; {
    if (!item.roles) return true; // 没有角色限制,所有人都能看
    return item.roles.some((role) =&gt; user?.roles?.includes(role));
  });

  return (
    &lt;Sidebar&gt;
      {/* ... */}
      &lt;SidebarMenu&gt;
        {filteredNav.map((item) =&gt; {
          // ...
        })}
      &lt;/SidebarMenu&gt;
      {/* ... */}
    &lt;/Sidebar&gt;
  );
}

这样一来,普通用户登录后,就看不到 “权限管理” 这个菜单项了。

4.2 外部链接和分割线

有时候侧边栏需要放外部链接(比如文档、帮助中心),或者用分割线把菜单分组。shadcn/ui Sidebar 也支持:

&lt;SidebarGroup&gt;
  &lt;SidebarGroupLabel&gt;主要功能&lt;/SidebarGroupLabel&gt;
  &lt;SidebarGroupContent&gt;
    &lt;SidebarMenu&gt;
      {/* 主要菜单项 */}
    &lt;/SidebarMenu&gt;
  &lt;/SidebarGroupContent&gt;
&lt;/SidebarGroup&gt;

&lt;SidebarGroup&gt;
  &lt;SidebarGroupLabel&gt;帮助支持&lt;/SidebarGroupLabel&gt;
  &lt;SidebarGroupContent&gt;
    &lt;SidebarMenu&gt;
      &lt;SidebarMenuItem&gt;
        &lt;SidebarMenuButton asChild&gt;
          &lt;a href="https://docs.example.com" target="_blank" rel="noopener"&gt;
            &lt;BookOpen className="h-4 w-4" /&gt;
            &lt;span&gt;使用文档&lt;/span&gt;
            &lt;ExternalLink className="ml-auto h-3 w-3" /&gt;
          &lt;/a&gt;
        &lt;/SidebarMenuButton&gt;
      &lt;/SidebarMenuItem&gt;
      &lt;SidebarMenuItem&gt;
        &lt;SidebarMenuButton asChild&gt;
          &lt;a href="mailto:[email protected]"&gt;
            &lt;HelpCircle className="h-4 w-4" /&gt;
            &lt;span&gt;联系我们&lt;/span&gt;
          &lt;/a&gt;
        &lt;/SidebarMenuButton&gt;
      &lt;/SidebarMenuItem&gt;
    &lt;/SidebarMenu&gt;
  &lt;/SidebarGroupContent&gt;
&lt;/SidebarGroup&gt;

4.3 搜索框和快捷操作

很多后台会在侧边栏放一个搜索框,或者全局搜索(Cmd+K)。shadcn/ui 有 Command 组件可以做这个:

import { Command, CommandInput, CommandList, CommandEmpty, CommandGroup, CommandItem } from "@/components/ui/command";

&lt;SidebarGroup&gt;
  &lt;SidebarGroupContent&gt;
    &lt;Command className="rounded-lg border shadow-md"&gt;
      &lt;CommandInput placeholder="搜索菜单..." /&gt;
      &lt;CommandList&gt;
        &lt;CommandEmpty&gt;未找到结果&lt;/CommandEmpty&gt;
        &lt;CommandGroup heading="建议"&gt;
          {navConfig.map((item) =&gt; (
            &lt;CommandItem key={item.href} onSelect={() =&gt; router.push(item.href)}&gt;
              &lt;item.icon className="mr-2 h-4 w-4" /&gt;
              {item.title}
            &lt;/CommandItem&gt;
          ))}
        &lt;/CommandGroup&gt;
      &lt;/CommandList&gt;
    &lt;/Command&gt;
  &lt;/SidebarGroupContent&gt;
&lt;/SidebarGroup&gt;

五、性能优化与最佳实践

最后聊几个实战中的优化点。

Server Components 优先

Next.js App Router 默认所有组件都是 Server Component。Sidebar 的静态部分(比如 Logo、固定的菜单项)可以保持为 Server Component,只有需要交互的部分(路由高亮、折叠状态)才用 "use client"

我的做法是:

  • AppSidebar 标记为 "use client"(因为用了 usePathname
  • SidebarHeaderSidebarFooter 里的静态部分单独抽成 Server Component
  • 导航配置在服务端生成,传给客户端组件

这样可以减少客户端 JS 体积。

懒加载大型菜单

如果你的后台有几十个菜单项,可以考虑懒加载。用 React.lazy 或者 Next.js 的 dynamic

import dynamic from "next/dynamic";

const AdminMenu = dynamic(() =&gt; import("./admin-menu"), {
  loading: () =&gt; &lt;SidebarMenuSkeleton /&gt;,
});

不过说实话,大多数后台的菜单项都不会太多,这个优化场景比较少见。

可访问性要点

shadcn/ui 的 Sidebar 基于 Radix UI,可访问性基本内置了。但你还是要注意几点:

  1. 图标 + 文字:不要只用图标,屏幕阅读器用户看不见
  2. Focus 可见:不要覆盖默认的 focus 样式
  3. 键盘导航:Tab 和方向键应该能正常工作

Radix UI 帮你处理了大部分,但如果你自定义了组件,记得测试一下键盘导航。


六、常见问题

Q1: Sidebar 状态刷新后丢失?

如果你把 SidebarProvider 放在 Dashboard Layout 而不是 Root Layout,路由切换时状态会重置。把 Provider 提升到 Root Layout 就行。

Q2: 移动端 Sidebar 怎么自动关闭?

shadcn/ui 的 Sidebar 在移动端会自动变成 Sheet。你需要在菜单项点击后手动关闭:

const { setOpenMobile } = useSidebar();

&lt;SidebarMenuButton
  onClick={() =&gt; setOpenMobile(false)}
&gt;

Q3: 如何自定义 Sidebar 宽度?

用 CSS 变量:

&lt;Sidebar
  style={{
    "--sidebar-width": "280px",
    "--sidebar-width-mobile": "100%",
  }}
&gt;

或者在 sidebar.tsx 里改 SIDEBAR_WIDTH 常量。


总结

shadcn/ui Sidebar 配合 Next.js Layout,搭建后台骨架确实高效。关键点回顾:

  1. 组件体系:理解 SidebarProvider、Sidebar、SidebarContent 等组件的职责
  2. Layout 整合:用 Route Groups 隔离不同布局,SidebarProvider 放 Root Layout
  3. 配置驱动:导航菜单存配置文件,组件根据配置渲染,方便维护
  4. 路由高亮usePathname() + isActive prop,简单直接
  5. 权限控制:配置里加 roles,渲染时过滤

这套架构我用了好几个项目,扩展性不错。新增页面只需要在 navConfig 里加一条,剩下的交给组件处理。

有问题欢迎在评论区讨论。下期我会写 shadcn/ui DataTable 的实战用法,感兴趣的可以关注一下。

搭建 shadcn/ui Sidebar + Next.js Layout 后台骨架

从零开始搭建一个可扩展的后台管理系统布局,包含侧边栏、路由高亮和权限控制

⏱️ 预计耗时: 45 分钟

  1. 1

    步骤1: 安装 shadcn/ui 并添加 Sidebar 组件

    运行 CLI 命令初始化项目并添加组件:

    ```bash
    npx shadcn@latest init
    npx shadcn@latest add sidebar
    ```

    安装过程会询问样式配置,选择默认即可。完成后 components/ui 目录下会生成 sidebar.tsx 文件。
  2. 2

    步骤2: 配置 Root Layout

    在 app/layout.tsx 中包裹 SidebarProvider:

    • 导入 SidebarProvider 组件
    • 在 body 标签内包裹 {children}
    • 设置 lang="zh-CN" 语言属性

    这样可以确保 Sidebar 状态在全局保持持久化。
  3. 3

    步骤3: 创建 Dashboard Layout

    在 app/(dashboard)/layout.tsx 创建后台专用布局:

    • 使用 Route Groups 语法 (dashboard)
    • 引入 AppSidebar 和 SidebarInset
    • 添加顶部 Header 和面包屑导航

    Route Groups 不影响 URL 结构,/dashboard 路径直接映射为根路径。
  4. 4

    步骤4: 定义导航配置

    创建 lib/navigation.ts 配置文件:

    • 定义 NavItem 接口(title、href、icon、badge)
    • 导出 navConfig 数组
    • 可选添加 roles 字段实现权限控制

    配置驱动的方式让新增菜单只需修改一处。
  5. 5

    步骤5: 实现 AppSidebar 组件

    创建 components/app-sidebar.tsx:

    • 使用 "use client" 标记为客户端组件
    • 通过 usePathname 获取当前路由
    • 遍历 navConfig 渲染菜单项
    • 匹配路由时设置 isActive 属性高亮
  6. 6

    步骤6: 添加权限控制(可选)

    实现 RBAC 权限过滤:

    • 在 NavItem 接口添加 roles 字段
    • 在 AppSidebar 中通过 useAuth 获取用户角色
    • 使用 filter 方法过滤菜单项

    没有 roles 字段的菜单默认对所有用户可见。

常见问题

shadcn/ui Sidebar 和 Ant Design 的侧边栏有什么区别?
shadcn/ui 采用 Copy-paste 模式,组件代码直接放你项目里,完全可控。Ant Design 是完整的设计系统,开箱即用但定制成本高。如果你需要高度定制,选 shadcn/ui;如果追求快速开发,选 Ant Design。
SidebarProvider 应该放在 Root Layout 还是 Dashboard Layout?
推荐放在 Root Layout(app/layout.tsx)。这样 Sidebar 的折叠状态可以在页面间保持。如果放在 Dashboard Layout,每次路由切换状态都会重置。
如何实现移动端 Sidebar 自动关闭?
移动端 Sidebar 会自动变成 Sheet 抽屉模式。你需要在菜单项点击时调用:

```tsx
const { setOpenMobile } = useSidebar();
&lt;SidebarMenuButton onClick={() =&gt; setOpenMobile(false)}&gt;
```

这样点击菜单项后抽屉会自动收起。
如何自定义 Sidebar 宽度?
有两种方式:

1. 使用 CSS 变量(推荐):
```tsx
&lt;Sidebar style={{ "--sidebar-width": "280px" }} /&gt;
```

2. 修改 sidebar.tsx 中的 SIDEBAR_WIDTH 常量

CSS 变量方式更灵活,可以针对不同 Sidebar 设置不同宽度。
如何实现多级菜单?
使用 Collapsible 组件包裹 SidebarMenuItem:

• CollapsibleTrigger 包含一级菜单按钮
• CollapsibleContent 包含 SidebarMenuSub 和二级菜单项
• ChevronDown 图标指示可展开状态

完整代码见文章 3.5 节。
shadcn/ui Sidebar 的可访问性如何?
基于 Radix UI 构建,内置完整的可访问性支持:键盘导航(Tab/方向键)、ARIA 属性、Focus 管理。你只需要确保图标和文字同时存在,不要只用图标。

参考资料

9 分钟阅读 · 发布于: 2026年3月27日 · 修改于: 2026年3月27日

评论

使用 GitHub 账号登录后即可评论

相关文章