一套 token 讲清深浅两套主题 封面
返回上一级

一套 token 讲清深浅两套主题

写作时间:2026-09-05
# Tailwind
# 设计系统

深色与浅色主题最怕的不是做不出来,而是做出来之后两套样式各自生长:改一处忘一处,半年后没人敢动。

语义优先,颜色靠边

组件里不允许出现任何具体颜色——只允许语义:

  • 画布 canvas、卡面 surface / surface-strong / surface-muted
  • 文字 ink / ink-2 / ink-3 / ink-4(按重要度递减)
  • 发丝线 hairline / hairline-soft
  • 强调 accent / accent-2 及其派生

颜色本身(比如「靛蓝 #6366f1」)只活在主题定义里,组件永远只见语义名。

一套定义,两套取值

:root {
  --app-ink: #0f172a;        /* 浅色:墨蓝黑 */
}
.dark {
  --app-ink: #f4f6fb;        /* 深色母版:near-white */
}

再用 Tailwind v4 的 @theme inline 把变量映射成工具类:

@theme inline {
  --color-ink: var(--app-ink);
}

inline 的关键在于工具类输出的是 var(--app-ink) 引用而非构建期常量——于是切换 .dark 这个 class,全站即时生效,组件零改动。

组件层的三条纪律

  1. 禁止 dark: 变体。出现 dark: 就说明某个颜色没被 token 化。
  2. 禁止内联 hex。同理,颜色只允许以 token 形式出现。
  3. 对比度按最坏情况算。深浅两套都要过 WCAG AA,尤其注意「最浅文字色 × 最亮背景色」的组合。

派生色怎么办

半透明的「卡面白」「发丝白」这类派生色,直接在 token 层定义透明度版本(rgb(255 255 255 / 0.4)),而不是在使用处临时叠 /40 修饰符——这样深浅两套主题可以给同一个语义名不同的透明度(深色下发丝线 12%,浅色下 40%,视觉密度才对等)。

小结

主题系统的本质是把「颜色决策」从组件层挪走。组件只回答「这里是主文字还是次要文字」,主题层回答「主文字在这套主题下是什么颜色」。决策分层了,两套主题才可能永远同步。