3.1 令牌体系的两层结构
shadcn 的主题分两层:底层是原始 CSS 变量(--primary 等),上层是 Tailwind 通过 @theme 暴露的工具类(bg-primary)。组件永远用工具类,你换肤只改底层变量。
- 设计令牌(Design Token)
- 把"颜色/圆角/间距"等设计决策抽象成命名变量,UI 引用变量而非硬编码值。改令牌即改全局,是设计系统的基石。
- 语义化命名(Semantic Token)
- shadcn 不用
--zinc-900这种"具体颜色名",而用--primary、--muted这种"用途名"。换基色时组件无需改动。 - foreground 配对
- 几乎每个背景令牌都配一个前景:
--primary/--primary-foreground。前者作背景色,后者作其上的文字色,保证对比度。 - oklch 色彩空间
- v4 主题默认用
oklch(L C H)表示颜色,感知均匀、明度可控,比 hsl 更适合生成协调的调色板。 - --radius 令牌
- 全局圆角基准。组件用
rounded-lg(=--radius)、rounded-md(略小)派生,改一个值统一控制圆角风格。 - chart-1..5 / sidebar-*
- 为图表和侧边栏预留的一组语义令牌,Chart 与 Sidebar 组件专用,可独立配色。
3.2 完整令牌清单
| 令牌 | 用途 |
|---|---|
| --background / --foreground | 页面底色 / 主文字 |
| --card / --card-foreground | 卡片背景 / 卡片文字 |
| --popover / --popover-foreground | 弹层背景 / 弹层文字 |
| --primary / --primary-foreground | 主色(按钮等)/ 其上文字 |
| --secondary / --secondary-foreground | 次要色 / 其上文字 |
| --muted / --muted-foreground | 弱化背景 / 次要文字 |
| --accent / --accent-foreground | 强调背景(hover)/ 文字 |
| --destructive | 危险/删除色(红) |
| --border / --input / --ring | 边框 / 输入框边 / 焦点环 |
| --radius | 全局圆角基准 |
3.3 明暗主题:一套变量两套值
亮色定义在 :root,暗色定义在 .dark。html 上有无 dark class 决定用哪套:
/* globals.css */
:root {
--background: oklch(1 0 0); /* 白 */
--foreground: oklch(0.145 0 0); /* 近黑 */
--primary: oklch(0.205 0 0);
--muted-foreground: oklch(0.556 0 0);
}
.dark {
--background: oklch(0.145 0 0); /* 近黑 */
--foreground: oklch(0.985 0 0); /* 近白 */
--primary: oklch(0.985 0 0);
--muted-foreground: oklch(0.708 0 0);
}
关键点
组件里写的是 bg-background text-foreground——它不知道自己是明是暗。切换 html 的 .dark class,变量值一变,整站瞬间换肤,无需组件感知。
3.4 主题切换按钮
"use client";
import { Moon, Sun } from "lucide-react";
import { useTheme } from "next-themes";
import { Button } from "@/components/ui/button";
export function ModeToggle() {
const { setTheme, resolvedTheme } = useTheme();
return (
<Button variant="outline" size="icon"
onClick={() => setTheme(resolvedTheme === "dark" ? "light" : "dark")}>
<Sun className="h-4 w-4 dark:hidden" />
<Moon className="hidden h-4 w-4 dark:block" />
<span className="sr-only">切换主题</span>
</Button>
);
}
3.5 自定义调色板
默认是中性黑白。想要品牌色(比如把 primary 换成蓝色),只需覆盖 --primary 及其 foreground:
:root {
/* 蓝色主色,foreground 用白字保证对比 */
--primary: oklch(0.55 0.22 264);
--primary-foreground: oklch(0.985 0 0);
--ring: oklch(0.55 0.22 264);
}
.dark {
--primary: oklch(0.65 0.20 264);
--primary-foreground: oklch(0.145 0 0);
}
用官方主题生成器
ui.shadcn.com 的 Themes 页面能可视化调色并一键复制这整段 CSS 变量。tweakcn 等社区工具也能生成完整主题。改完直接粘进 globals.css 即可。
3.6 调整圆角风格
:root {
--radius: 0.5rem; /* 默认偏圆 */
/* 想要更方硬的极简感 → 改成 0.25rem 或 0 */
/* 想要很圆润 → 改成 1rem */
}
组件用 rounded-lg / rounded-md / rounded-sm,它们通过 @theme 全部派生自 --radius,改一处即全局统一。
3.7 多主题(不止明暗)
可以扩展成"多套配色主题"——给不同 class 挂不同变量:
.theme-rose {
--primary: oklch(0.58 0.22 15);
}
.theme-emerald {
--primary: oklch(0.6 0.15 160);
}
/* 给 body 或某个容器加 class 即切换主题,可与 .dark 叠加 */
3.8 令牌到工具类的映射
/* @theme inline 把变量桥接成 Tailwind 颜色 */
@theme inline {
--color-primary: var(--primary);
--color-primary-foreground: var(--primary-foreground);
--color-muted-foreground: var(--muted-foreground);
--color-destructive: var(--destructive);
--color-border: var(--border);
}
/* 于是可以写 bg-primary / text-primary-foreground / border-border */
3.9 小结与下一步
- 令牌分两层:底层 CSS 变量 + 上层 Tailwind 工具类,组件只用工具类。
- 语义命名(primary/muted)+ foreground 配对,换基色/换肤时组件零改动。
- 明暗靠
.darkclass 切换整套变量;自定义品牌色只覆盖--primary等即可。 - 下一章进入表单组件——Button/Input/Select/Switch 等,以及
cva变体与cn()的实战。