切换语言
切换主题

shadcn/ui 安装与主题定制完全指南(含 CSS 变量)

说实话,第一次用 shadcn/ui 的时候,我被它”不是 npm 包”这个设定搞蒙了。复制代码到项目里?这听起来也太原始了吧?

用了几次之后才发现,这才是它厉害的地方——你拥有所有组件的源码,想怎么改就怎么改,不用担心版本冲突,也不用被组件库的设计限制死。

今天我们就聊聊 shadcn/ui 的安装配置和主题定制,重点是怎么用 CSS 变量实现品牌化的设计系统。读完这篇文章,你应该能在 5 分钟内搞定基础配置,再花个把小时把主题调整成你想要的样子。


一、快速安装:两种方式

方式一:CLI 一键初始化(推荐)

新项目直接用这个命令:

npx shadcn@latest init

运行后会问你一堆问题:用 TypeScript 还是 JavaScript?选哪种风格?默认主题是什么?全程交互式,跟着提示选就行。

安装完之后,你的项目里会多几个文件:

  • components.json - 配置文件
  • lib/utils.ts - 工具函数
  • components/ui/ - 组件存放目录

想添加组件也简单,比如要个按钮:

npx shadcn@latest add button

组件代码会自动复制到 components/ui/button.tsx,你直接 import 用就行。

这里有个坑:如果你项目已经开发了一段时间,tailwind.config.js 和 globals.css 里可能已经写了不少东西。shadcn 的 init 命令会覆盖这些文件,所以最好在项目刚开始的时候就装。

有个博主说得挺对:把 shadcn/ui 当成项目的”第一批依赖”来装,别等到后面再加。血的教训。

方式二:手动安装(适合现有项目)

如果你的项目已经成型,CLI 覆盖配置风险太大,那就手动装。

分几步走:

第一步:确保装了 Tailwind CSS

shadcn 的组件都是用 Tailwind 写的,没装的话先装 Tailwind。这个不多说,官网教程很清楚。

第二步:安装依赖

npm install class-variance-authority clsx tailwind-merge
npm install lucide-react

class-variance-authority(简称 CVA)是个好东西,后面做组件变体时你会用到的。

第三步:配置路径别名

tsconfig.json 里加上:

{
  "compilerOptions": {
    "paths": {
      "@/*": ["./*"]
    }
  }
}

这样你就可以用 @/components/ui/button 这种方式引入组件,不用写一堆 ../../../

第四步:创建 components.json

在项目根目录新建这个文件:

{
  "style": "new-york",
  "rsc": true,
  "tailwind": {
    "config": "tailwind.config.ts",
    "css": "app/globals.css",
    "baseColor": "neutral",
    "cssVariables": true
  },
  "aliases": {
    "components": "@/components",
    "utils": "@/lib/utils"
  }
}

cssVariables: true 这行很关键,表示我们要用 CSS 变量做主题,而不是 Tailwind 的 utility class。

第五步:添加样式

globals.css 里加上 shadcn 的基础样式,这个后面讲主题时会详细说。


二、理解主题系统:CSS 变量怎么玩

shadcn/ui 的主题系统基于一个简单的约定:每个颜色都有 backgroundforeground 两个变量。

啥意思?举个例子:

:root {
  --primary: 222.2 47.4% 11.2%;
  --primary-foreground: 210 40% 98%;
}

--primary 是按钮的背景色,--primary-foreground 是按钮上的文字颜色。这样配对的好处是,你只要改一个变量,相关的所有组件都会跟着变。

CSS 变量列表

shadcn/ui 默认定义了这些变量:

变量用途
--background页面背景色
--foreground页面文字色
--card卡片背景
--card-foreground卡片文字
--popover弹出层背景
--popover-foreground弹出层文字
--primary主色(按钮、链接)
--primary-foreground主色上的文字
--secondary次要颜色
--secondary-foreground次要颜色上的文字
--muted柔和背景
--muted-foreground柔和文字
--accent强调色
--accent-foreground强调色上的文字
--destructive危险操作(删除按钮)
--destructive-foreground危险操作上的文字
--border边框
--input输入框
--ring焦点环

看着挺多,其实理解了 background/foreground 这个套路,就很好记。

HSL 格式的秘密

你可能注意到,shadcn 的颜色值不是标准的 HSL 格式:

/* ❌ 标准 HSL */
--primary: hsl(222.2, 47.4%, 11.2%);

/* ✅ shadcn 格式 */
--primary: 222.2 47.4% 11.2%;

为啥要写成这种”裸”的格式?

因为 Tailwind 支持透明度修饰符,比如 bg-primary/50 表示 50% 透明度的主色。如果你的变量是完整的 hsl() 格式,这个功能就用不了。

写成裸格式之后,Tailwind 会自动给你加上 hsl() 和透明度。聪明的设计。


三、定制你的品牌主题

方法一:直接改 CSS 变量

最简单的方式,打开 globals.css,找到 :root 部分,改颜色值就行。

比如你想把主色从默认的蓝色改成紫色:

:root {
  --primary: 270 60% 60%;
  --primary-foreground: 0 0% 100%;
}

.dark {
  --primary: 270 60% 70%;
  --primary-foreground: 0 0% 0%;
}

改完保存,所有用了 bg-primary 的按钮、链接都会变成紫色。

方法二:用 OKLCH 颜色空间(Tailwind v4)

如果你用的是 Tailwind v4,可以考虑用 OKLCH 颜色空间。相比 HSL,OKLCH 的颜色感知更接近人眼,生成的色阶更均匀。

:root {
  --primary: oklch(0.6 0.2 270);
  --primary-foreground: oklch(0.98 0 0);
}

这里 oklch(0.6 0.2 270) 的三个参数是:

  • 0.6 - 亮度(0-1)
  • 0.2 - 彩度(0-0.4 左右)
  • 270 - 色相角度(0-360)

方法三:在线工具生成

觉得自己配颜色太麻烦?可以用在线工具。

推荐这个:Shadcn Theme Generator

选个主色,工具会自动生成完整的 CSS 变量组,包括亮色和暗色两套。复制粘贴到 globals.css 就行。


四、暗色模式配置

用 next-themes 实现主题切换

shadcn/ui 本身不带主题切换功能,但可以用 next-themes 这个库来搞定。

先安装:

npm install next-themes

然后在 layout.tsx 里配置:

import { ThemeProvider } from "next-themes"

export default function RootLayout({
  children,
}: {
  children: React.ReactNode
}) {
  return (
    <html lang="zh" suppressHydrationWarning>
      <body>
        <ThemeProvider
          attribute="class"
          defaultTheme="system"
          enableSystem
        >
          {children}
        </ThemeProvider>
      </body>
    </html>
  )
}

几个关键点:

  • suppressHydrationWarning 必须加,不然会有水合警告
  • attribute="class" 表示用 class 名切换主题
  • defaultTheme="system" 表示默认跟随系统
  • enableSystem 开启系统主题检测

创建主题切换按钮

useTheme hook 获取当前主题和切换函数:

import { useTheme } from "next-themes"
import { Moon, Sun } from "lucide-react"

export function ThemeToggle() {
  const { theme, setTheme } = useTheme()

  return (
    <button
      onClick={() => setTheme(theme === "dark" ? "light" : "dark")}
      className="p-2 rounded-md hover:bg-accent"
    >
      {theme === "dark" ? <Sun size={20} /> : <Moon size={20} />}
    </button>
  )
}

默认暗色模式

如果你想让网站默认就是暗色模式,有两种方式:

方式一:硬编码 dark class

<html lang="zh" className="dark">

这样写的话,主题就固定成暗色了,没法切换。

方式二:设置默认主题

<ThemeProvider
  attribute="class"
  defaultTheme="dark"  // 默认暗色
  enableSystem={false} // 关闭系统检测
>

这样用户可以手动切换,但初始状态是暗色。


五、高级定制:组件变体

用 CVA 创建自定义变体

有时候你需要给按钮加几种样式,比如”危险”、“成功”、“渐变”。用 CVA 可以很方便地定义这些变体。

import { cva, type VariantProps } from "class-variance-authority"

const buttonVariants = cva(
  "inline-flex items-center justify-center rounded-md text-sm font-medium transition-colors",
  {
    variants: {
      variant: {
        default: "bg-primary text-primary-foreground hover:bg-primary/90",
        destructive: "bg-destructive text-destructive-foreground hover:bg-destructive/90",
        outline: "border border-input bg-background hover:bg-accent hover:text-accent-foreground",
        secondary: "bg-secondary text-secondary-foreground hover:bg-secondary/80",
        ghost: "hover:bg-accent hover:text-accent-foreground",
        link: "text-primary underline-offset-4 hover:underline",
      },
      size: {
        default: "h-10 px-4 py-2",
        sm: "h-9 rounded-md px-3",
        lg: "h-11 rounded-md px-8",
        icon: "h-10 w-10",
      },
    },
    defaultVariants: {
      variant: "default",
      size: "default",
    },
  }
)

export interface ButtonProps
  extends React.ButtonHTMLAttributes<HTMLButtonElement>,
    VariantProps<typeof buttonVariants> {}

然后在组件里用:

<button className={buttonVariants({ variant: "destructive", size: "lg" })}>
  删除
</button>

不要直接改 shadcn 组件源码

这是个最佳实践问题。

shadcn 的组件代码在你自己的项目里,想怎么改都行。但建议不要直接改原始文件,而是创建包装组件。

为啥?因为 shadcn 经常更新组件,如果你改了原始文件,更新的时候就得手动 merge,很麻烦。

更好的方式:

// components/brand-button.tsx
import { Button } from "@/components/ui/button"
import { cva } from "class-variance-authority"

const brandButtonVariants = cva("...", {
  variants: {
    brand: {
      primary: "bg-brand-primary text-white",
      secondary: "bg-brand-secondary text-black",
    },
  },
})

export function BrandButton({ brand, ...props }) {
  return <Button className={brandButtonVariants({ brand })} {...props} />
}

这样原始的 Button 组件保持不变,你创建了自己的 BrandButton,以后 shadcn 更新也不会影响你的定制。


六、常见问题和踩坑

问题一:安装后样式不生效

检查这几件事:

  1. globals.css 是否在 layout.tsx 里 import 了?
  2. Tailwind 的 content 配置是否包含了 components/**/*
  3. components.json 的路径配置对不对?

问题二:主题切换时闪烁

这个通常是水合不匹配导致的。确保:

  1. <html> 标签加了 suppressHydrationWarning
  2. ThemeProvider 包裹了整个应用
  3. 不要在服务端渲染时读取 theme(会 undefined)

问题三:CSS 变量不生效

可能的原因:

  1. 变量名写错了(注意是 --primary-foreground 不是 --primaryForeground
  2. 没有对应的 .dark 样式
  3. 变量值格式不对(要用裸 HSL 或 OKLCH)

问题四:组件样式冲突

如果你的项目已经有一套样式系统,和 shadcn 的可能有冲突。解决方案:

  1. 给 shadcn 组件加 namespace(比如 shadcn-button
  2. 调整 Tailwind 的 layer 优先级
  3. 用 CVA 创建自己的变体,不依赖默认样式

七、总结

shadcn/ui 的安装配置其实挺简单的,关键是理解它”复制代码而非依赖包”的设计理念。这样做的好处是完全可控,坏处是每个项目都得自己维护一套组件代码。

主题定制方面,CSS 变量系统设计得很优雅,你只要改几个变量值,整个应用的配色就会跟着变。配合 next-themes,亮色暗色切换也就几行代码的事。

最后几个建议:

  1. 新项目优先用 CLI 初始化,省去手动配置的麻烦
  2. 用语义化的颜色变量,比如 primary、secondary,而不是具体的颜色名
  3. 测试亮色和暗色模式下的对比度,确保可读性
  4. 创建包装组件而非修改源码,方便后续更新

下次你需要快速搭建一个带主题的 UI 时,试试 shadcn/ui。复制粘贴的快乐,谁用谁知道。


shadcn/ui 安装与主题定制

从零开始安装 shadcn/ui,配置主题系统,实现品牌化设计

⏱️ 预计耗时: 30 分钟

  1. 1

    步骤1: CLI 快速初始化

    在新项目中运行安装命令:

    • npx shadcn@latest init
    • 选择 TypeScript/New York 风格/默认主题
    • 等待 CLI 完成配置
  2. 2

    步骤2: 修改品牌主色

    编辑 globals.css 中的 CSS 变量:

    • 打开 app/globals.css
    • 找到 :root 下的 --primary 变量
    • 修改为你的品牌色(HSL 或 OKLCH 格式)
    • 同时修改 --primary-foreground 确保对比度
  3. 3

    步骤3: 配置暗色模式

    安装并配置 next-themes:

    • npm install next-themes
    • 在 layout.tsx 中添加 ThemeProvider
    • 设置 suppressHydrationWarning 避免水合警告
    • 创建主题切换组件
  4. 4

    步骤4: 创建组件变体

    使用 CVA 定义自定义样式:

    • 安装 class-variance-authority
    • 定义 variants 和 defaultVariants
    • 在组件中应用 buttonVariants()
    • 保持原始 shadcn 组件不变

常见问题

shadcn/ui 和传统 UI 组件库有什么区别?
shadcn/ui 不是 npm 包,而是复制组件源码到你的项目。好处是完全可控、可定制,不用担心版本冲突;坏处是每个项目都要自己维护组件代码。
为什么建议在新项目初始化时就安装 shadcn/ui?
因为 shadcn 的 init 命令会覆盖 tailwind.config.js 和 globals.css。如果你的项目已经开发了一段时间,这些文件里的配置会被覆盖,所以越早装越好。
CSS 变量为什么要写成裸格式而不是标准 HSL?
裸格式(如 222.2 47.4% 11.2%)支持 Tailwind 的透明度修饰符,比如 bg-primary/50 表示 50% 透明度。如果是完整的 hsl() 格式,这个功能就用不了。
如何修改品牌主色?
打开 globals.css,找到 :root 下的 --primary 和 --primary-foreground 变量,改成你的品牌色值。改完后所有用 bg-primary 的组件都会自动更新。
暗色模式为什么会出现闪烁?
通常是水合不匹配导致的。确保 html 标签加了 suppressHydrationWarning 属性,ThemeProvider 正确包裹了整个应用,不要在服务端渲染时读取 theme。
应该直接修改 shadcn 组件源码吗?
不建议。shadcn 经常更新组件,如果你改了原始文件,更新时需要手动 merge,很麻烦。更好的方式是创建包装组件,保持原始组件不变。

参考资料

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

评论

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

相关文章