design-tokens-theming

Compare original and translation side by side

🇺🇸

Original

English
🇨🇳

Translation

Chinese

Design Tokens & Theming

Design Token 与主题化

Overview

概述

A SaaS UI looks "designed" when every color, space, and font size comes from a small, named scale — not arbitrary values sprinkled per component. Define tokens once as CSS variables, expose them through Tailwind, and let dark mode be a swap of variable values, not a rewrite of class names.
当每种颜色、间距和字号都来自一套精简且命名明确的 scale,而不是在各组件中随意散落具体数值时,SaaS UI 才显得“经过精心设计”。将这些 token 一次性定义为 CSS 变量,通过 Tailwind 对外暴露,并让深色模式只需交换变量值,而不必重写 class 名称。

When to use

使用场景

  • Starting a new app and deciding colors/spacing/typography.
  • Components use raw hex codes or one-off pixel values.
  • You need dark mode or multi-brand theming.
  • 启动新应用并确定颜色、间距和排版。
  • 组件使用原始十六进制色值或零散的 px 值。
  • 需要深色模式或多品牌主题化。

Semantic tokens (the key idea)

语义化 token(核心思路)

Name tokens by role, not by hue.
--primary
,
--muted
,
--destructive
— not
--blue-500
. Roles let you re-theme without touching components. shadcn/ui uses exactly this convention:
css
/* globals.css */
:root {
  --background: 0 0% 100%;
  --foreground: 240 10% 3.9%;
  --primary: 240 5.9% 10%;
  --primary-foreground: 0 0% 98%;
  --muted: 240 4.8% 95.9%;
  --muted-foreground: 240 3.8% 46.1%;
  --destructive: 0 84.2% 60.2%;
  --border: 240 5.9% 90%;
  --radius: 0.5rem;
}
.dark {
  --background: 240 10% 3.9%;
  --foreground: 0 0% 98%;
  --primary: 0 0% 98%;
  --primary-foreground: 240 5.9% 10%;
  --muted: 240 3.7% 15.9%;
  --muted-foreground: 240 5% 64.9%;
  --border: 240 3.7% 15.9%;
}
Store HSL channels (no
hsl(...)
wrapper) so Tailwind can add opacity:
hsl(var(--primary) / 0.5)
.
按角色而非色相命名 token。
--primary
、
--muted
、
--destructive
——而不是
--blue-500
。借助角色,你无需修改组件即可重新设置主题。shadcn/ui 正是采用这一约定:
css
/* globals.css */
:root {
  --background: 0 0% 100%;
  --foreground: 240 10% 3.9%;
  --primary: 240 5.9% 10%;
  --primary-foreground: 0 0% 98%;
  --muted: 240 4.8% 95.9%;
  --muted-foreground: 240 3.8% 46.1%;
  --destructive: 0 84.2% 60.2%;
  --border: 240 5.9% 90%;
  --radius: 0.5rem;
}
.dark {
  --background: 240 10% 3.9%;
  --foreground: 0 0% 98%;
  --primary: 0 0% 98%;
  --primary-foreground: 240 5.9% 10%;
  --muted: 240 3.7% 15.9%;
  --muted-foreground: 240 5% 64.9%;
  --border: 240 3.7% 15.9%;
}
存储 HSL 通道值(不要使用
hsl(...)
包装),以便 Tailwind 可以添加透明度:
hsl(var(--primary) / 0.5)
。

Wire tokens into Tailwind

将 token 接入 Tailwind

ts
// tailwind.config.ts
export default {
  darkMode: "class",
  theme: {
    extend: {
      colors: {
        background: "hsl(var(--background))",
        foreground: "hsl(var(--foreground))",
        primary: { DEFAULT: "hsl(var(--primary))", foreground: "hsl(var(--primary-foreground))" },
        muted: { DEFAULT: "hsl(var(--muted))", foreground: "hsl(var(--muted-foreground))" },
        border: "hsl(var(--border))",
      },
      borderRadius: { lg: "var(--radius)", md: "calc(var(--radius) - 2px)" },
    },
  },
};
Now
bg-primary text-primary-foreground
works in both themes automatically.
ts
// tailwind.config.ts
export default {
  darkMode: "class",
  theme: {
    extend: {
      colors: {
        background: "hsl(var(--background))",
        foreground: "hsl(var(--foreground))",
        primary: { DEFAULT: "hsl(var(--primary))", foreground: "hsl(var(--primary-foreground))" },
        muted: { DEFAULT: "hsl(var(--muted))", foreground: "hsl(var(--muted-foreground))" },
        border: "hsl(var(--border))",
      },
      borderRadius: { lg: "var(--radius)", md: "calc(var(--radius) - 2px)" },
    },
  },
};
现在,
bg-primary text-primary-foreground
会在两种主题中自动生效。

Dark mode toggle

深色模式切换

Use
next-themes
(or a tiny class toggler) and
darkMode: "class"
. Never hard-code
dark:
colors when a semantic token already flips —
bg-background
beats
bg-white dark:bg-zinc-950
.
使用
next-themes
(或一个简单的 class 切换器),并设置
darkMode: "class"
。当语义化 token 已经能够完成切换时,绝不要硬编码
dark:
颜色——
bg-background
优于
bg-white dark:bg-zinc-950
。

Scales to standardize

需要标准化的 scale

  • Spacing: stick to Tailwind's 4px scale (
    p-2
    ,
    gap-4
    ,
    space-y-6
    ). Avoid
    p-[13px]
    .
  • Typography: a 5–6 step type scale (
    text-sm
    →
    text-3xl
    ); set
    text-muted-foreground
    for secondary text.
  • Radius/shadow: one
    --radius
    token; 2–3 shadow steps.
  • **间距:**坚持使用 Tailwind 的 4px scale(
    p-2
    、
    gap-4
    、
    space-y-6
    )。避免使用
    p-[13px]
    。
  • **排版:**采用 5–6 级字号 scale(
    text-sm
    →
    text-3xl
    );次要文本设置为
    text-muted-foreground
    。
  • **圆角/阴影:**使用一个
    --radius
    token;设置 2–3 级阴影。

Pitfalls

常见问题

  • Raw hex/px values per component → inconsistent UI that can't be re-themed.
  • dark:
    overrides everywhere
    instead of flipping tokens once.
  • Naming tokens by color (
    --blue
    ) instead of role (
    --primary
    ).
  • Wrapping
    hsl()
    in the variable
    — breaks Tailwind opacity modifiers.
  • Too many shades — a bloated palette is harder to keep consistent than a tight one.
  • 在每个组件中使用原始十六进制/px 值 → 导致 UI 不一致,且无法重新设置主题。
  • 到处使用
    dark:
    覆盖值
    ,而不是一次性切换 token。
  • 按颜色命名 token(
    --blue
    ),而不是按角色命名(
    --primary
    )。
  • 在变量中包装
    hsl()
    → 会破坏 Tailwind 的透明度修饰符。
  • 色阶过多 → 过于庞大的调色板比精简的调色板更难保持一致。

Hand-off

交付

A token layer that
responsive-layout
,
accessible-components
, and every component skill build on — change a value once, the whole app re-themes.
交付一个 token 层,供
responsive-layout
、
accessible-components
以及所有组件 skill 在其上构建——修改一次数值,整个应用都会重新应用主题。