AI 编码代理能写出可用的代码,但它们产出的 UI 看起来很通用。每个生成的应用看起来像是用同一套默认组件搭的,因为确实是。代理对你的品牌没有任何锚点。让它"创建一个次级按钮",它就编一个颜色。让它"把应用做成暗色模式",它就检查每个组件,猜该替换哪些颜色。
问题的另一半是保真度。你在设计工具里设计了东西,交接出去,回来的代码不匹配。间距不对,颜色接近但不准,排版尺度没了。设计有结构,但交接把它丢了。
这就是一整套工具和格式正在试图解决的问题。共同的主线是 design tokens,而把它们带给 AI 代理的格式叫 DESIGN.md。这篇帖子是我在试图理解这些零件如何拼合时学到的。
Token 承载意图,不只是值
一个 design token 是一个有名字的值。不是在按钮上存 #2563EB,而是存 color.primary。不是在一个角上存 8px,而是存 radius.md。
区别在于意图。当一个人,或一个东西,看一个原始值时,它只看到值。一个蓝矩形,8px 圆角。它得猜:这是主按钮吗?这个蓝在别处用过吗?8px 是一个刻意决定还是某人随手打的一个数?
有了 token,意图就可读了。color.primary 说明了颜色是干什么的。radius.md 说明这是尺度的一部分,不是任意一个数。读这个的开发者不用猜。读这个的 AI 代理不用猜。
同一个 token,color.primary,在 CSS 里变成 --color-primary,在 iOS 上变成 Color.primary,在 Tailwind 里变成 theme.colors.primary。一个决定,用每个平台的语言表达。这就是人们把 design token 叫单一真相源的意思。
一个设计系统是整个结构。token 是其中的变量。关系是这样的:
Design System
│
├── Foundations
│ │
│ ├── Tokens
│ │ ├── Colors
│ │ ├── Typography
│ │ └── Spacing
│ │
│ └── Themes
│ ├── Light
│ └── Dark
│
├── Components
│ ├── Button
│ ├── Card
│ └── Input
│
└── Documentation
├── Usage rules
└── Examples值得指出的是,Light 和 Dark 不是设计系统本身。它们是其中的主题。token 是让主题切换的变量。组件消费 token。设计系统是整个东西。
Token 不是用来画的
这点我之前理解反了。我以为 token 是为了让设计工具把东西画对。不是。token 是用来描述画东西背后的决定的。
设计工具里的按钮还是一个矩形加文字。有了 token,矩形不存 #2563EB。它存一个引用:fill → color.primary。几何是一样的。变的是设计文件现在带着原因,不只是结果。
一个组件不绑定到一个原始值。它绑定到一个语义 token,语义 token 指向一个原始 token。改原始的,下游全部跟着。这就是层级的意义。顶部一个改动传播到所有地方。
当设计走向代码时这很重要。没有 token,代码生成器对像素做逆向工程。它看到一个蓝矩形就写 background: #2563EB。有了 token,它读 color.primary 就写 background: var(--color-primary)。生成的代码看起来像开发者会写的,不是像素转储。
Token 不够
一个全是 token 的 JSON 文件告诉你值。它不告诉你这些值为什么存在,什么时候用,什么时候不用。你没法在一个 tokens.json 文件里表达"primary 只用于主 CTA"。
这就是 DESIGN.md 填补的空缺。它出自 Google Stitch,Google 的 AI 设计工具,于 2026 年 4 月在 Apache 2.0 下开源。它的核心是一个文件,放在你项目的根目录,给 AI 代理一个对你设计系统的持久理解。
一个 DESIGN.md 文件有两层。上面是 YAML front matter:你的 token,写成结构化的值。颜色、排版、间距、圆角、组件。下面是 markdown 散文,解释 token 是干什么的以及怎么应用。
token 给代理精确的值。散文告诉它这些值为什么存在。
DESIGN.md 里的一个按钮组件看起来是这样:
components:
button-primary:
backgroundColor: "{colors.tertiary}"
textColor: "{colors.on-tertiary}"
rounded: "{rounded.sm}"
padding: 12px{colors.tertiary} 引用语法来自 W3C Design Tokens Format Module。改一次 colors.tertiary,按钮就在所有引用它的地方更新。跟 Penpot 这样的设计工具原生使用的同一个标准。
八个部分
一个 DESIGN.md 有八个部分,固定顺序:
- Overview
- Colors
- Typography
- Layout
- Elevation & Depth
- Shapes
- Components
- Do's and Don'ts
Do's and Don'ts 部分是散文证明自己价值的地方。"卡片上永远不要用投影。""按钮标签始终用句首大写。"这些是纯 token 无法表达的约束。一个 JSON token 文件说值是什么。DESIGN.md 说拿它们做什么,以及避免什么。
为什么它流行起来了
它放在你仓库的根目录,跟 README.md 和 CLAUDE.md / AGENTS.md 并排。代理已经在读项目根目录的 markdown 了。README.md 给人解释项目。CLAUDE.md 和 AGENTS.md 告诉代理怎么行为。DESIGN.md 告诉它们 UI 应该长什么样。
GitHub 上的 awesome-design-md 仓库,一个从真实生产站点提取的现成 DESIGN.md 文件集合,在发布几周内就超过了 58,000 星。12.6% 的 fork 率,意味着大约每 8 个发现它的人里就有 1 个把文件复制到了自己的项目里。那不是收藏。那是采纳。
你不用从零写。有目录。getdesign.md 有 300 多个来自真实生产站点的 DESIGN.md 分析。Open Design 提供 151 个设计系统包。designmd.app 索引了 461 个。你挑一个接近你品牌的,然后改改。
规范还是 alpha。颜色值只有 sRGB。像 Oklch 和 Display P3 这样的广色域格式,W3C 标准都支持,但还没进 DESIGN.md。CLI 可以导出到 Tailwind v3/v4 和 W3C DTCG 格式,所以 token 能流进你现有的管道。
设计工具放在哪里
DESIGN.md 是真相源。设计工具是你应用 token 做屏幕的地方。Penpot 和 Figma 是两个例子,还有别的。它们对同一个概念采取不同的方法。
Penpot 是第一个原生集成 W3C Design Tokens Format Module 的设计工具。跟 DESIGN.md 的 token 引用所受启发的同一个标准。在 Penpot 里,token 活在集合里,也就是相关 token 的集合。你的基础放在一个集合里:基础颜色、间距值、圆角。你的语义 token 放在另一个里:primary、success、error。你把集合组合成不同上下文的主题。Light 是一个主题。Dark 是一个主题。Light 和 Dark 不是不同的设计系统,它们是应用到同一批 token 名字上的不同主题。
因为 Penpot 原生说 W3C 格式,你的 token 以标准格式导出。没有翻译层。
Figma 采取不同的方法。Figma Variables 是 Figma 对 token 概念的原生实现。Figma 用集合的地方 Penpot 用集合,用模式的地方 Penpot 用主题。一个 Figma 集合包含相关变量,模式为不同上下文存储平行值。别名建立原始到语义到组件的层级:一个像 text-primary 的语义 token 指向一个像 gray-900 的原始 token,改原始的就更新下游所有东西。
区别在于 Figma 的变量用私有格式。用 W3C 标准格式从 Figma 拿出 token 需要像 Tokens Studio 这样的插件。Penpot 原生导出 W3C。两者实现的是同一个概念。一个说标准,一个说自己的方言。
重点不是你用哪个工具。重点是 token,不管工具怎么存,都应该追溯到同一个真相源:你的设计系统,编码在 DESIGN.md 里。
Design to Code
这里是零件连接的地方。
旧的交接是:在工具里设计,导出文件,交给开发者,祈祷他读对规范。开发者对像素做逆向工程,猜意图。
新的交接是:把你的设计系统编码在 DESIGN.md 里。在工具里用 token 设计。你的 AI 代理读 DESIGN.md,生成使用同样 token 名字的生产代码。
Design system → DESIGN.md → AI agent → production code代理从 YAML token 拿到精确值。它从散文拿到规则。它不用猜一个蓝矩形是不是主按钮,因为 DESIGN.md 说 button-primary 用 colors.tertiary。生成的代码用 var(--color-tertiary),不是硬编码的 hex 值。
在实践中,流程双向跑。一个新项目从 token 开始。你定义 token,写 DESIGN.md,让你的 AI 代理从仓库读 DESIGN.md。代理然后通过它的 CLI 或 MCP 把 token 上传到设计工具。Penpot 原生导入 W3C 格式。Figma 需要 Tokens Studio 这样的插件。设计工具和代理读同一个源。
一个现有项目走另一个方向。你把当前设计拆成 token,标准化命名,从它们写一个 DESIGN.md。然后你把那些 token 上传到设计工具。如果项目根本没有设计,只有代码,AI 代理可以读当前代码库,提取前端,把设计移进设计工具。从那里起,设计工具和代码库共享同一个源。
设计工具和代理之间的连接通过 MCP 跑,或者通过工具的 CLI,如果它有的话。pen.dev 启动一个本地 MCP 服务器,两边都通过它连接。
pencil.dev(现在是 pen.dev)
pen.dev 更进一步,把设计工具和代码编辑器合并成一个环境。它是一个活在你 IDE 里的设计画布,不是一个单独的应用。设计文件(.pen)放在你仓库里,跟你的代码并排。Git 跟踪它们。你分支和合并设计跟分支和合并代码一样。
当 pen.dev 运行时,它启动一个本地 MCP 服务器。你的 AI 代理,不管是 Claude Code、Cursor、Codex 还是 OpenCode,通过 MCP 连接,可以读取和修改设计文件。pen.dev 里的变量映射到 CSS 自定义属性。你视觉上设计,代理生成代码,两边读同样的 token。
社区围绕这个建了工具。有一个叫 pencil-atelier 的 Claude Code 插件,它提取 .pen 文件的视觉语言,把它写到项目根目录的一个 design.md 里。从那以后,每次设计和代码生成过程都读同一个合同。会话之间不用手动重新交接。
还没解决的
Atlassian 在生产中测试了 DESIGN.md,发现它一次加载所有东西,不是按需。跟按组件获取上下文的 MCP 服务器相比,DESIGN.md 大约多用了 92% 的 token,运行之间的方差是 2.7 倍。它是一个便携快照,不是一个完整设计系统管道的替代。规范是 alpha。工具还很粗糙。
起作用的是形状。你把设计决定编码一次,用一个人和代理都能读的格式。你在用同样 token 的工具里设计。你的代理读同一个文件,生成同样名字的代码。被替代的是翻译层,那个以前是一个人拿着 Figma 文件和猜测的翻译层。
