Chapter 03

主题与设计令牌

吃透 shadcn 的 CSS 变量令牌体系,学会明暗切换、自定义调色板与 Tailwind 映射,打造属于自己的换肤系统。

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 小结与下一步