3、设计系统基础:设计Token的概念、颜色与字体规范、间距与阴影体系、设计Token的实现
好,咱们进入正题。这一章聊的是设计系统的地基——设计Token。说白了,Token就是设计语言里的「原子变量」。你想想看,一个按钮的颜色、一段文字的间距、一个卡片的阴影,如果每个地方都硬编码,那改起来就是灾难。
我在项目中遇到过最典型的例子:一个产品有3个品牌,每个品牌的主色不同。如果没有Token,你得全局搜索#1890ff然后手动替换。有了Token,改一个变量值,全站跟着变。这就是Token的价值。
3.1 什么是设计Token
设计Token,本质上就是「设计属性的抽象命名」。它把具体的数值(比如16px、#333)包装成一个有语义的名字(比如spacing-md、color-text-primary)。
我个人习惯把Token分成三个层级:
- 基础Token:最底层的原子值,比如
color-blue-500: #1890ff、font-size-16: 16px - 语义Token:基于基础Token的语义化映射,比如
color-primary: color-blue-500、font-body: font-size-16 - 组件Token:具体到组件的变量,比如
button-bg: color-primary、card-padding: spacing-md
核心原则:基础Token只存原始值,语义Token做映射,组件Token做最终消费。这样改一个基础值,所有层级自动更新。
3.2 颜色与字体规范
颜色这块,我踩过不少坑。早期我直接写#1890ff、#f5222d,后来发现设计师给的色板有10个灰度、6个主色、4个功能色。如果每个都硬编码,维护成本极高。
我的做法是:先定义色板,再定义语义色。
色板定义
// 基础色板
--color-blue-50: #e6f7ff;
--color-blue-100: #bae7ff;
--color-blue-200: #91d5ff;
--color-blue-300: #69c0ff;
--color-blue-400: #40a9ff;
--color-blue-500: #1890ff;
--color-blue-600: #096dd9;
--color-blue-700: #0050b3;
--color-blue-800: #003a8c;
--color-blue-900: #002766;
// 中性色
--color-gray-50: #fafafa;
--color-gray-100: #f5f5f5;
--color-gray-200: #e8e8e8;
--color-gray-300: #d9d9d9;
--color-gray-400: #bfbfbf;
--color-gray-500: #8c8c8c;
--color-gray-600: #595959;
--color-gray-700: #434343;
--color-gray-800: #262626;
--color-gray-900: #1f1f1f;
语义色映射
// 语义色
--color-primary: var(--color-blue-500);
--color-primary-hover: var(--color-blue-400);
--color-primary-active: var(--color-blue-600);
--color-success: #52c41a;
--color-warning: #faad14;
--color-error: #f5222d;
--color-text-primary: var(--color-gray-800);
--color-text-secondary: var(--color-gray-500);
--color-bg-primary: #ffffff;
--color-bg-secondary: var(--color-gray-50);
我的经验:语义色不要超过12个。超过这个数,设计师和开发都会晕。我曾经维护过一个有30个语义色的系统,最后连设计师自己都分不清color-info-light和color-info-bg的区别。
字体规范
字体Token相对简单,但容易忽略「行高」和「字重」的配合。我建议把字体相关的Token统一管理:
// 字体族
--font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, 'Helvetica Neue', Arial, sans-serif;
--font-family-mono: 'SF Mono', 'Fira Code', 'Consolas', monospace;
// 字号
--font-size-xs: 12px;
--font-size-sm: 14px;
--font-size-md: 16px;
--font-size-lg: 20px;
--font-size-xl: 24px;
--font-size-2xl: 32px;
// 行高
--line-height-xs: 1.4;
--line-height-sm: 1.5;
--line-height-md: 1.6;
--line-height-lg: 1.8;
// 字重
--font-weight-regular: 400;
--font-weight-medium: 500;
--font-weight-semibold: 600;
--font-weight-bold: 700;
注意:字号不要用奇数。12、14、16、20、24、32这些偶数在渲染时更清晰。我曾经用过13px,结果在部分浏览器上出现了模糊的渲染问题。
3.3 间距与阴影体系
间距体系,说白了就是「步进值」。我习惯用4px作为基础步进。为什么是4?因为大多数屏幕的像素是整数倍,4px能整除8、12、16、20、24,适配性最好。
间距Token
--spacing-xxs: 4px;
--spacing-xs: 8px;
--spacing-sm: 12px;
--spacing-md: 16px;
--spacing-lg: 24px;
--spacing-xl: 32px;
--spacing-2xl: 48px;
--spacing-3xl: 64px;
嗯,这里要注意:间距Token不要用奇数。4、8、12、16、24、32、48、64,这些值在网格系统中能完美对齐。我见过有人用--spacing-sm: 10px,结果和8px的网格对不齐,整个页面看起来「差一点」。
阴影体系
阴影Token我一般分三个层级:低、中、高。每个层级对应不同的视觉深度。
// 阴影
--shadow-sm: 0 1px 2px rgba(0, 0, 0, 0.06), 0 1px 3px rgba(0, 0, 0, 0.1);
--shadow-md: 0 4px 6px rgba(0, 0, 0, 0.06), 0 2px 4px rgba(0, 0, 0, 0.08);
--shadow-lg: 0 10px 15px rgba(0, 0, 0, 0.1), 0 4px 6px rgba(0, 0, 0, 0.05);
--shadow-xl: 0 20px 25px rgba(0, 0, 0, 0.1), 0 10px 10px rgba(0, 0, 0, 0.04);
避坑指南:阴影的透明度不要超过0.15。我曾经为了「突出」某个卡片,用了0.3的透明度,结果在深色模式下阴影变成了「黑块」,特别难看。保持透明度在0.06~0.12之间最安全。
3.4 设计Token的实现
终于到了代码环节。Token的实现方式有很多种,我个人最推荐CSS自定义属性(CSS Variables)配合JavaScript变量。这样既能运行时修改,又能静态编译。
CSS实现
:root {
// 基础色板
--color-blue-500: #1890ff;
// ... 其他基础色
// 语义色
--color-primary: var(--color-blue-500);
--color-text-primary: #262626;
// 间距
--spacing-md: 16px;
// 阴影
--shadow-sm: 0 1px 2px rgba(0, 0, 0, 0.06);
}
// 使用
.button {
background-color: var(--color-primary);
padding: var(--spacing-md);
box-shadow: var(--shadow-sm);
}
JavaScript实现(主题切换)
// token定义
const tokens = {
light: {
'--color-primary': '#1890ff',
'--color-bg-primary': '#ffffff',
'--color-text-primary': '#262626',
},
dark: {
'--color-primary': '#177ddc',
'--color-bg-primary': '#141414',
'--color-text-primary': '#e8e8e8',
}
};
// 切换主题
function applyTheme(themeName) {
const theme = tokens[themeName];
const root = document.documentElement;
Object.entries(theme).forEach(([key, value]) => {
root.style.setProperty(key, value);
});
}
// 调用
applyTheme('dark');
关键点:CSS Variables的兼容性很好(IE11除外),而且支持运行时修改。配合JavaScript,可以实现无闪烁的主题切换。我在一个百万级DAU的产品中用过这套方案,性能完全没问题。
设计Token的命名规范
命名规范我建议统一用--category-property-modifier的格式:
| 分类 | 命名示例 | 说明 |
|---|---|---|
| 颜色 | --color-primary |
主色 |
| 字体 | --font-size-md |
中号字号 |
| 间距 | --spacing-lg |
大间距 |
| 阴影 | --shadow-sm |
小阴影 |
| 圆角 | --radius-md |
中号圆角 |
| 边框 | --border-width-thin |
细边框 |
警告:不要用--color-1、--spacing-a这种无意义的命名。我见过一个项目,Token命名全是--c1、--c2、--s1、--s2,三个月后连作者自己都看不懂哪个是主色、哪个是背景色。
好了,这一章的内容就到这里。设计Token看似简单,但它是整个组件库的基石。颜色、字体、间距、阴影,每一个细节都决定了最终产品的视觉一致性。下一章我们会聊「组件设计原则」,到时候我会分享更多实战中的踩坑经验。