This commit is contained in:
1 parent
c1541b80df
commit
b4c7908029
97 files changed
+12403
-154
No files matched your search
@@ -0,0 +1,144 @@
|
||||
---
|
||||
title: "设计令牌:把设计规范变成可执行的代码"
|
||||
date: "2025-02-20"
|
||||
summary: "设计系统最容易失效的地方不是设计稿,而是设计与代码之间的翻译过程。用 Tailwind v4 的 @theme 让令牌成为唯一事实来源。"
|
||||
tags: ["设计系统", "Tailwind", "CSS"]
|
||||
draft: false
|
||||
---
|
||||
|
||||
每个团队都遇到过这种对话:
|
||||
|
||||
> 「这个按钮的圆角怎么是 8px?设计稿是 12px。」
|
||||
> 「我随手写的,没注意。」
|
||||
|
||||
问题不在于谁写错了,而在于**规范停留在文档里,没有变成代码**。
|
||||
|
||||
## 令牌是设计与代码之间的契约
|
||||
|
||||
一份设计规范通常包含颜色、字体、间距、圆角、阴影。这些值如果只写在 Figma 和 Markdown 里,它们就只是**建议**;只有当它们变成代码里有名字的变量,才成为**约束**。
|
||||
|
||||
关键在于给值起名,而不是记住值:
|
||||
|
||||
| 不要这样写 | 要这样写 |
|
||||
| --- | --- |
|
||||
| `border-radius: 12px` | `rounded-lg` |
|
||||
| `color: #0a0a0a` | `text-ink` |
|
||||
| `padding: 24px` | `p-xl` |
|
||||
|
||||
前者的语义是「12 像素」,后者的语义是「标准卡片圆角」。当设计决定把标准圆角改成 14px 时,前者需要全局搜索替换,后者只需要改一行。
|
||||
|
||||
## Tailwind v4 的 @theme
|
||||
|
||||
Tailwind v4 把配置从 JavaScript 搬回了 CSS。用 `@theme` 声明的变量会同时具备两种身份:**CSS 自定义属性**和**生成工具类的依据**。
|
||||
|
||||
```css
|
||||
@theme {
|
||||
--color-ink: #0a0a0a;
|
||||
--color-brand-green: #00d4a4;
|
||||
--radius-lg: 12px;
|
||||
--spacing-xl: 24px;
|
||||
}
|
||||
```
|
||||
|
||||
声明之后,`text-ink`、`bg-brand-green`、`rounded-lg`、`p-xl` 这些工具类会自动可用,同时也可以在原生 CSS 里直接引用:
|
||||
|
||||
```css
|
||||
.card {
|
||||
border-radius: var(--radius-lg);
|
||||
padding: var(--spacing-xl);
|
||||
border: 1px solid var(--color-hairline);
|
||||
}
|
||||
```
|
||||
|
||||
这就是单一事实来源的价值——工具类和手写 CSS 读的是**同一份变量**,不可能对不上。
|
||||
|
||||
## 颜色:先定语义,再定色值
|
||||
|
||||
新手容易直接把调色板搬进来:`--color-gray-100`、`--color-gray-200`、`--color-blue-500`。这能工作,但它没有表达**用途**。
|
||||
|
||||
更好的方式是按角色命名:
|
||||
|
||||
```text
|
||||
--color-ink 主文字
|
||||
--color-steel 三级文字
|
||||
--color-canvas 页面背景
|
||||
--color-surface 次级表面
|
||||
--color-hairline 分割线
|
||||
--color-brand-green 品牌强调色
|
||||
```
|
||||
|
||||
这样命名之后,暗色模式的实现会变得非常自然——同一个语义变量换一组值即可,组件代码一行都不用改。反过来,如果组件里写的是 `bg-gray-100`,切暗色时就只能靠 `dark:` 前缀逐处覆盖。
|
||||
|
||||
## 字号的陷阱:行高和字距
|
||||
|
||||
排版令牌如果只存字号,会丢掉一半信息。同一个 16px,正文需要 1.5 倍行高,标题可能只需要 1.2。
|
||||
|
||||
Tailwind v4 允许把行高和字距直接绑定到字号上:
|
||||
|
||||
```css
|
||||
@theme {
|
||||
--text-heading-2: 36px;
|
||||
--text-heading-2--line-height: 1.2;
|
||||
--text-heading-2--letter-spacing: -0.5px;
|
||||
}
|
||||
```
|
||||
|
||||
现在 `text-heading-2` 一次带出行高和字距,不需要每次手写 `text-4xl leading-tight tracking-tight`。
|
||||
|
||||
还有一个细节值得注意:**负字距应该随字号递增**。大标题需要更紧的字距才显得结实,小字号则应该放松甚至归零。
|
||||
|
||||
```text
|
||||
72px → letter-spacing: -2px
|
||||
36px → letter-spacing: -0.5px
|
||||
18px → letter-spacing: 0
|
||||
```
|
||||
|
||||
## 间距:八点网格之外的例外
|
||||
|
||||
八点网格(8px 递增)是常见约定,但直接用它做类名会很难读:`p-3` 到底是 12px 还是 16px?
|
||||
|
||||
按尺码命名的可读性更好:
|
||||
|
||||
```css
|
||||
@theme {
|
||||
--spacing-xxs: 4px;
|
||||
--spacing-xs: 8px;
|
||||
--spacing-sm: 12px;
|
||||
--spacing-md: 16px;
|
||||
--spacing-lg: 20px;
|
||||
--spacing-xl: 24px;
|
||||
--spacing-section: 64px;
|
||||
--spacing-hero: 120px;
|
||||
}
|
||||
```
|
||||
|
||||
注意最后两个:`section` 和 `hero` 是**语义间距**。区块之间 64px、Hero 区 120px,这类值在页面上反复出现,给它们名字比记数字可靠得多。
|
||||
|
||||
## 用类型约束收口
|
||||
|
||||
令牌再多,也需要有人保证组件只从令牌里取值,而不是随手写魔法数字。`class-variance-authority` 能把变体定义收敛到一处:
|
||||
|
||||
```ts
|
||||
const buttonVariants = cva("inline-flex items-center rounded-full", {
|
||||
variants: {
|
||||
variant: {
|
||||
primary: "bg-primary text-on-primary",
|
||||
secondary: "border border-hairline text-ink",
|
||||
accent: "bg-brand-green text-primary",
|
||||
},
|
||||
size: {
|
||||
md: "px-lg py-[10px] text-button-md",
|
||||
sm: "px-md py-xs text-body-sm",
|
||||
},
|
||||
},
|
||||
defaultVariants: { variant: "primary", size: "md" },
|
||||
});
|
||||
```
|
||||
|
||||
组件使用者只能在 `primary | secondary | accent` 里选,写不出 `bg-[#0a0a0a]` 这种绕过令牌的代码。**把约束交给类型系统,比交给代码评审更可靠。**
|
||||
|
||||
## 一条经验
|
||||
|
||||
设计令牌的价值不在于「变量很多」,而在于**边界清晰**。真正需要严格约束的是颜色和间距——它们决定了视觉的一致性;而字号阶梯可以适度宽松,因为排版本来就需要灵活性。
|
||||
|
||||
最后一点:令牌的命名应该描述**意图**,而不是外观。`brand-green` 比 `mint` 好,`danger` 比 `red` 好。外观会变,意图通常不会。
|
||||
Reference in new issue
Block a user