用 CSS 变量搭一套设计令牌,顺手拿到深色模式
发布 修订 分类 coding
当前浏览器未执行 JavaScript,下面是本篇的 Markdown 原文,内容一字未改。
很多项目的深色模式之所以维护得痛苦,是因为颜色散落在两百个选择器里。解法不是写两套样式,而是先把颜色收进一层抽象。
<!-- more -->
## 令牌分三层
1. **原始值**:`#14b8a6`、`14px`,只是素材。
2. **语义令牌**:`--accent`、`--ink-2`、`--line`,描述用途而非颜色。
3. **组件变量**:组件内部专用,一般不需要全局。
只有第二层需要暴露给主题。
```css
:root{
--ink:#0e1726; /* 主文字 */
--ink-3:#6b7c93; /* 次要文字 */
--paper:#f7f8fa; /* 背景 */
--line:#e3e8ef; /* 分割线 */
--accent:#14b8a6; /* 强调色 */
--r:14px; /* 圆角 */
--sh-2:0 8px 24px -12px rgba(16,24,40,.18);
}
html[data-theme="dark"]{
--ink:#e8edf4;
--ink-3:#8b9bb0;
--paper:#0b1017;
--line:#1f2b3a;
--accent:#2dd4bf;
--sh-2:0 10px 30px -14px rgba(0,0,0,.7);
}
```
注意深色模式下我不只改了颜色,还改了阴影——浅色的柔和阴影在深色背景上会变成一团脏斑,必须换成更重更近的阴影。
## 别用纯黑和纯白
- 深色背景用 `#0b1017` 这类带蓝的深灰,纯黑 `#000` 会让文字边缘产生光晕错觉。
- 浅色背景用 `#f7f8fa` 这类微暖灰,纯白 `#fff` 在夜里刺眼。
- 强调色在深色模式下要**提亮**:`#14b8a6` 在浅底上很清楚,放到深底上会发闷,换成 `#2dd4bf` 就好。
## 让深色不闪烁
主题在样式表加载前就必须定下来,否则会闪一下白。在 `<head>` 里放一段同步脚本:
```js
(function () {
var p = localStorage.getItem('lz-theme') || 'auto';
var t = p === 'auto'
? (matchMedia('(prefers-color-scheme: dark)').matches ? 'dark' : 'light')
: p;
document.documentElement.setAttribute('data-theme', t);
})();
```
同时监听系统主题变化,但只在用户选了 `auto` 时才跟随:
```js
matchMedia('(prefers-color-scheme: dark)').addEventListener('change', sync);
```
## 顺手拿到的三件好处
1. 想换主题色,改一个变量,全站生效。
2. 打印样式只要在 `@media print` 里覆写同一批变量,把 `--paper` 设成白色即可。
3. 组件样式不再关心自己长什么样,只关心用哪个令牌,迁移组件不会带着颜色走。
> [!NOTE]
> 变量名不要用颜色词。`--blue` 会在你把它改成绿色的时候变成谎话,`--accent` 不会。