刚学前端时候,我以为 CSS 变量就是 var(--primary-color) 替代 #6d28d9—换个地方写颜色值而已。

后来重构博客样式,我才意识到:把所有颜色、间距、圆角集中定义在一处,改一个值全站联动—这比满文件搜索替换 #6d28d9 靠谱太多了。

这篇文章记录我在实际项目中用 CSS 变量管理设计系统过程。

设计 Token:给设计取名字

设计 Token 就是给设计决策取个名字。我的博客定义了五类:

颜色

:root {
  /* 主色 */
  --color-primary: #425AEF;
  --color-primary-light: #6B7FF5;
  --color-primary-dark: #3149D1;

  /* 语义色 */
  --color-success: #22c55e;
  --color-warning: #f59e0b;
  --color-error: #ef4444;

  /* 中性色 */
  --color-text: #2c2c2c;
  --color-text-secondary: #666;
  --color-text-muted: #999;
  --color-border: #e5e7eb;

  /* 背景层次 */
  --bg-body: #f5f6f8;
  --bg-card: #ffffff;
  --bg-elevated: #ffffff;
}

命名用语义而不是颜色值:--color-text--gray-900 好。暗色模式下文字颜色会变,但”正文文字”这个语义不会变。

间距

:root {
  --gap-xs: 4px;
  --gap-sm: 8px;
  --gap-md: 16px;
  --gap-lg: 24px;
  --gap-xl: 32px;
  --gap-2xl: 48px;
  --gap-3xl: 64px;
}

4px 倍数步长。以前随手写 margin: 13px,现在不会再出现了。

字体

:root {
  --font-sans: 'Noto Sans SC', system-ui, sans-serif;
  --font-serif: 'Noto Serif SC', Georgia, serif;
  --font-mono: 'JetBrains Mono', 'Fira Code', monospace;

  --text-xs: 0.75rem;
  --text-sm: 0.875rem;
  --text-base: 1rem;
  --text-lg: 1.125rem;
  --text-xl: 1.25rem;
  --text-2xl: 1.5rem;
  --text-3xl: 2rem;
}

圆角与阴影

:root {
  --radius-sm: 6px;
  --radius-md: 14px;
  --radius-lg: 20px;
  --radius-full: 9999px;

  --shadow-sm: 0 1px 2px rgba(0, 0, 0, 0.05);
  --shadow-md: 0 4px 6px rgba(0, 0, 0, 0.07);
  --shadow-lg: 0 10px 15px rgba(0, 0, 0, 0.1);
}

暗色模式:换个值就行

暗色模式有两种做法:CSS 变量切换和 prefers-color-scheme 媒体查询。

我选变量切换,因为可以手动切换,不只跟系统走:

[data-theme="dark"] {
  --color-text: #d8d8e4;
  --color-text-secondary: #a0a0b0;
  --color-text-muted: #6b6b7b;
  --color-border: #2d2d3d;

  --bg-body: #1a1a2e;
  --bg-card: #252540;
  --bg-elevated: #2d2d4a;
}

切换主题改一个属性就行:

document.documentElement.setAttribute('data-theme', isDark ? 'dark' : 'light');

暗色模式有几个容易踩坑:

  1. 别用纯白#fff 在深色背景上太刺眼,#d8d8e4 舒服得多
  2. 背景加色温。纯黑 #000 太冷,偏蓝紫 #1a1a2e 更柔和
  3. 阴影降透明度。暗色模式下阴影要更微妙
  4. 边框降对比。暗色边框与背景对比度应该比浅色模式低

间距一致性

重构前代码:

.hero { margin-bottom: 48px; }
.section { padding: 24px; }
.card { padding: 16px; gap: 8px; }
.footer { margin-top: 60px; }  /* 60px? 为什么不是 64px? */

重构后:

.hero { margin-bottom: var(--gap-2xl); }
.section { padding: var(--gap-lg); }
.card { padding: var(--gap-md); gap: var(--gap-sm); }
.footer { margin-top: var(--gap-3xl); }

60px 变成了 64px(--gap-3xl),肉眼看不出区别,但以后维护不需要猜”这个 60px 是怎么来”。

常见误区

所有值都用变量

一次性值(如 top: -2px)用硬编码更清晰。变量的意义在于复用和统一,不是变量越多越好。

嵌套变量过深

/* 调试噩梦 */
--color-primary: var(--blue-500);
--blue-500: var(--blue-base);
--blue-base: #425AEF;

最多一层间接引用。设计 Token 应该是扁平。

忽略 fallback

如果受众包含旧浏览器用户,提供 fallback:

.hero {
  color: #2c2c2c;           /* fallback */
  color: var(--color-text); /* 现代浏览器覆盖 */
}

实际效果

改完之后,我调主色调只需要改 --color-primary 一个值。改间距只需要调 --gap-*。暗色模式加一组变量覆盖就完事。

以前改个主色要全局搜索替换十几个文件,现在改一行。这就是变量化好处:省事,且不容易漏。

种下你的想法

在花园里留下一条评论,和这篇文章一起生长。

COMMENTS