DESIGN.md 设计哲学深度解析:为什么"文字叙述"才是视觉身份规范的核心
【免费下载链接】design.mdA format specification for describing a visual identity to coding agents. DESIGN.md gives agents a persistent, structured understanding of a design system.项目地址: https://gitcode.com/GitHub_Trending/de/design.md
DESIGN.md 是一种用于向编码 Agent 描述视觉身份的格式规范,而 PHILOSOPHY.md 记录了这套规范背后的核心设计哲学:设计的灵魂存在于文字叙述(prose)之中,设计令牌(tokens)只是支撑叙述的上下文。读完本文,你将理解为什么"一句具体的参照"胜过"一打精确的数值"、为什么负面约束(don'ts)能自动随引用而来,以及这个格式如何在不修改规范的前提下,通过用户自定义扩展生长出属于自己的设计语言——每一条结论都会结合本仓库的源码、测试与示例给出可验证的依据。
一句话抓住 DESIGN.md 的哲学核心
DESIGN.md captures how a design looks, feels, and behaves. The prose is where the design lives. Everything else in the document exists to support it.
翻译过来即:DESIGN.md 记录的是一个设计看起来、感觉起来、行为起来的样子;设计活在叙述文字里,文档中的其他一切(令牌、结构、规范)都只是为叙述服务。
这句开宗明义的话直接决定了 PHILOSOPHY.md 全文的论述方向,也解释了为什么 DESIGN.md 会同时包含两种截然不同的内容层——机器可读的 YAML 令牌与人类(以及 Agent)可读的 Markdown 叙述。
哲学落地的最小示例:Technical Handout
原文档用一个极简示例展示了"叙述即设计"的含义:
--- name: Technical Handout --- ## Overview A graduate-level computer science lecture handout in the tradition of an old established university. The audience is graduate students and research engineers reading a printed handout distributed at the beginning of a seminar. The handout is austere, informationally dense, and proudly unconcerned with first impressions. The audience knows why they are there and the handout's job is to do work, not to seduce.请注意这个示例的精妙之处:front matter 里只有一个name,没有任何颜色、字体、间距令牌。整份文档的设计意图完全由 Overview 中的三段叙述承载——"老牌大学研究生级别的课堂讲义""信息密集、毫不讨好第一印象"。"austere"(朴素克制)、"proudly unconcerned with first impressions"(骄傲地不关心第一印象)这些措辞,向 Agent 传递的是一种完整的态度,而非一组数值。
由此引出全文最重要的一句话:
生成设计质量的优劣,更多取决于"意图被描述得有多清晰",而非"数值有多精确"。
仓库层面的呼应:DESIGN.md 的双层结构
README.md 将这种哲学固化为格式本身:"A DESIGN.md file combines machine-readable design tokens (YAML front matter) with human-readable design rationale (markdown prose). Tokens give agents exact values. Prose tells themwhythose values exist and how to apply them."
而规范文档 docs/spec.md 中说得更直接:"The tokens are the normative values; the prose provides context for how to apply them."(令牌是规范性数值;叙述为如何应用它们提供上下文。)"normative / context" 的措辞与"价值观/为什么"的措辞一脉相承——数值负责精确,叙述负责意义,两者缺一不可。
Prose, not Tokens:叙述才是规范的主角
原文档明确划分了 DESIGN.md 的两大组成:tokens与prose。规范存在的意义是"描述设计语境"——既要让设计在不同生成会话之间保持一致性,又要为创造性探索留出空间。而其中prose 是最关键的部分。
一个完整的 Colors 对照示例
原文档给出了一个同时包含令牌与叙述的典型段落:
## Colors ```yaml # Tokens colors: paper: '#F4F0E4' ink: '#1E1A14' vermilion: '#C3402A' rule-gray: '#B8B0A2' ``` <!-- Prose --> A single-ink-plus-accent system. - **Paper** {colors.paper} is the canvas — warmed xerox stock, never pure white. - **Ink** {colors.ink} is graphite-warm and carries all typography, all rules, all diagram strokes; never pure black. - **Vermilion** {colors.vermilion} is the single accent and appears only inside diagrams and chart annotations — never on typography, never on page numerals, never on metadata of any kind. - **Rule gray** {colors.rule-gray} is reserved for hairline rules inside content (chart baselines, table dividers); never used as page-frame chrome.注意这里出现了 DESIGN.md 特有的语法:{colors.paper}这样的花括号令牌引用,让叙述能够动态指向 front matter 中的精确值。同一段文字里,"warmed xerox stock, never pure white"(温暖的复印纸质感,绝非纯白)这类描述承载了#F4F0E4这个十六进制值背后的全部理由——为什么是它、它应该被用在什么场合、绝对不能用在什么场合。
在仓库的真实示例 examples/atmospheric-glass/DESIGN.md 中可以看到同样的结构:front matter 定义了全套 Material 风格的颜色令牌(surface-container-lowest、on-primary、error-container等)与 Inter 字体排印令牌,正文则用 "vibrant-minimalist"、"frosted crystalline lenses"(磨砂水晶透镜)等叙述交代了玻璃拟态美学的完整意图。另一个测试夹具 packages/cli/src/linter/fixtures/ALPINE_OBSERVATORY.md 则展示了"Scientific Alpinism"(科学式阿尔卑斯登山)这种风格:#0a1325的深海军蓝被称为The Void(虚空),#f6bb81的古董黄铜被称为The Instrument(仪器)——数值与叙事互相锚定。
令牌是上下文,不是渲染指令
这是全文最容易被误解、也最值得强调的论点:
令牌值作为上下文存在,而非渲染指令。一般而言,规范并不要求也不建议你在规范里硬性规定令牌。
把令牌当作"叙述中引用的参考物",DESIGN.md 的职责就聚焦在"记录设计的本质"上,而不是去重复语言和工具生态早已耕耘几十年的工作——字体加载、颜色空间转换、间距计算这些事,交给 CSS、Tailwind、Figma 或设计令牌工具链去解决。本仓库恰好用代码证明了这种分工:packages/cli/src/linter/tailwind/v4/serialize.ts 与 packages/cli/src/linter/dtg/handler.ts 负责把令牌导出为 Tailwind v4 的@theme块或 W3C DTCGtokens.json——格式本身只负责定义与描述,真正的渲染由这些成熟的既有工具完成。
叙述语法的规范依据
从 docs/spec.md 可以看到这种"叙述为主、令牌为辅"的结构被固化为规范:
- Color:任何合法 CSS 颜色字符串(Hex、命名色、
rgb()/hsl()/hwb()、宽色域oklch()/oklab()/lch()/lab()、以及color-mix(in srgb, ...)混合);Hex#RRGGBB被推荐为默认写法。 - Dimension:带单位后缀的字符串,正式支持的单位为
px、em、rem(见 packages/cli/src/linter/spec-config.yaml 中的units定义)。 - Typography:
fontFamily、fontSize、fontWeight、lineHeight、letterSpacing、fontFeature、fontVariation七个属性;lineHeight既可写24px这类 Dimension,也可写1.6这种无单位倍数。 - Token Reference:
{path.to.token}花括号引用语法,在components内允许引用复合值(如{typography.label-md})。
一个具体参照,胜过一打形容词
原文档用一个尖锐的对比论证了"具体参照"的价值:
A design that references "A 1970s graduate lecture handout in the tradition of an old and established university" evokes a complete world: the one color of ink, the generous margins, the serif set at a reading size, and the absence of decoration. That single sentence carries more useful information than a dozen metric values. It carries the reasoning behind the values.
"一份 1970 年代老牌大学的研究生课堂讲义"这句话唤起的是一整个完整的世界:单一颜色的墨水、宽大的页边距、以阅读字号排版的衬线字体、以及完全没有装饰。这一句话携带的有用信息,超过一打度量值——因为它携带了数值背后的推理过程。
反过来:
"Modern, clean, trustworthy, premium" evokes nothing specific. A model creates something in the center of what those words describe, creating an output that is typically generic. Adjectives describe a region. A specific reference describes a point.
"现代、干净、可信、高级"这类形容词唤不起任何具体画面。模型会在这个词所描述的区域正中央生成一个东西——通常是平庸的、随大流的结果。形容词描述的是一个区域,具体参照描述的是一个点。
这个观点可以直接指导写作实践:与其罗列border-radius: 8px、box-shadow参数,不如写"像一本学术期刊的版面";与其堆砌"优雅、极简、专业",不如写"像 1970 年代老牌大学的课堂讲义"。Agent 对具体参照的理解能力,本质上来自它对现实世界物体的海量训练知识——命名一个对象,就等于同时传递了它的全部隐含属性。
在 examples/paws-and-paths/DESIGN.md、examples/totality-festival/DESIGN.md 这些示例中可以看到同样的手法:每个设计系统都用一句"它是什么"来锚定全部后续决策,而不是用形容词清单。
负面约束:你省略掉的东西定义了性格
"一个清晰的设计参照会自动携带它的限制。"原文档的论证如下:
A model knows what a lecture handout is, and it knows what a lecture handout is not. It does not glow or use a gradient. You don't have to list these. Naming the object names them, the same way naming a dog tells the model that dogs don't meow.
模型知道课堂讲义是什么,也知道它不是什么——它不会发光、不会用渐变。这些根本不需要你列举。命名即命名:就像你说了"狗",模型自然知道狗不会喵喵叫。
由此得出两条实用结论:
- 当参照足够具体时,负面约束是免费附赠的。一个具体的参照("讲座讲义")本身就排除了大量的错误方向。
- 有意的"不要做"清单是有用的,冗长的流水账式清单往往是描述过于模糊的信号。如果你需要靠十几条禁令才能防止 Agent 跑偏,通常说明你的正面描述还不够具体。
两者的理想配合是:"一个强参照 + 一份有意为之的 Do's and Don'ts 清单"。
原文档的完整 Do's and Don'ts 示例
## Do's and Don'ts - **Don't** add a hero moment to the title page. A real handout title page is the first page of content, not a magazine cover. - **Don't** reach for an italic standfirst beneath a large title. That is the Substack register. - **Don't** add corner ornaments, chapter marks, or abstract glyphs in the margins. - **Don't** color the page numeral or any other piece of metadata. Vermilion lives in diagrams only. - **Don't** use a display-class serif. One family at four modest sizes. - **Don't** use Bold. Anywhere. - **Don't** use sans-serif for any role other than monospace metadata. - **Don't** introduce dark mode, gradients, glows, glass surfaces, drop shadows, or rounded corners. - **Do** treat the handout as a printed object. The screen is the substrate; the design is the page. - **Do** keep vermilion inside diagrams. Its scarcity outside is what makes its presence inside meaningful. - **Do** trust modest size differences. The section title is only ~1.9× body, not 5× body. - **Do** let pages have visible white space. A page that ends two-thirds of the way down is correct, not under-filled.这份清单的每个条目都值得品味:它不是在罗列"不允许出现的 CSS 属性",而是在陈述性格——"标题页不是杂志封面""屏幕只是载体,设计是纸张本身""朱红色的稀缺性正是其内部出现时有意义的原因"。注意那些明确的参照点:"That is the Substack register"(那是 Substack 的腔调)、"One family at four modest sizes"(单一字体家族、四个克制的字号)、"~1.9× body"(正文的约 1.9 倍)。每条禁令都指向一个可被模型精确理解的参照物,而不是一个抽象形容词。
规范层面,docs/spec.md 将 "Do's and Don'ts" 列为八个标准小节中的最后一个(第 8 位),定位是"创建设计时的护栏(guardrails)";packages/cli/src/linter/spec-config.yaml 中的sections定义确认了它的规范名称与别名,而section-order这一条 lint 规则(见 packages/cli/src/linter/linter/rules/section-order.ts)会校验小节是否按规范顺序出现。
格式通过用户生长,而不是通过规范生长
结构最小主义:只标准化"通用到值得统一"的部分
原文档指出,规范定义的只是每一份 DESIGN.md 都共享的结构性最小集:
- 一个
name; - 一小撮足够通用、值得标准化的类别:
colors、typography、spacing、rounded、components。
除此之外,一切都是你的自由。格式接受你的设计系统需要的任何键、任何小节、任何结构。
规范在"一致性有帮助的地方"做标准化,在"灵活性更有帮助的地方"保持开放。原文档特别点名了这些开放领域:motion(动效)、iconography(图标)、elevation(层级)、text casing(文字大小写)、paragraph measure(段落行长)。
理由很实际:同一类令牌在不同团队里形态天差地别。一个团队的 motion 令牌是 CSS 动画曲线;另一个团队的则是以缓冲块(buffer blocks)为单位的音频域时间常数。正确的形态取决于每个系统自身,而格式本身已经允许你定义它。
Motion 扩展示例
原文档用"动效"这个自定义类别演示了如何不修改规范就完成扩展:
## Motion ```yaml motion: feedback: 120ms content: 250ms easing: 'cubic-bezier(0.2, 0, 0, 1)' ``` Transitions are quick and mechanical. Nothing bounces, nothing overshoots, nothing lingers. State changes should feel like a light switch, not a door closing. - Interactive feedback (hover, press, toggle): {motion.feedback}, always {motion.easing}. - Content transitions (page, panel, modal): {motion.content}, same curve. - Nothing in the UI animates longer than 300ms. If something takes longer, cut it. - Respect `prefers-reduced-motion`: all durations collapse to 0ms.这个示例完美体现了全部哲学:motion不是规范预定义的键(规范只定义了 colors/typography/spacing/rounded/components 五个令牌组),但格式允许它存在;{motion.feedback}引用语法与规范内令牌完全一致;而叙述部分——"像电灯开关,而不是关门"、"任何动画超过 300ms 就砍掉"、"尊重prefers-reduced-motion"——才是这份设计真正有灵魂的地方。
源码证据:扩展如何被 linter 优雅地接受
"格式接受任何键"不是一句空话,仓库的 lint 规则实现给出了两条精妙的佐证:
1. 自定义键保持静默。packages/cli/src/linter/linter/rules/unknown-key.ts 中的unknown-key规则只对"长得像已知键拼写错误"的顶层键发出警告:它用莱文斯坦编辑距离(levenshtein,见 packages/cli/src/linter/linter/rules/levenshtein.ts)把未知键与colors/typography/spacing/rounded/components等 schema 键做相似度比对,距离阈值MAX_TYPO_DISTANCE = 2——colours:会被提示"是不是想写colors:?",而motion:、iconography:这类真正的自定义扩展键不会被误报。这正是"格式生长于用户"在代码层的落地:有意的扩展零打扰,无意的拼写错误被温柔捕获。
2. 令牌样值兜底提醒。packages/cli/src/linter/linter/rules/token-like-ignored.ts 中的token-like-ignored规则更进一步:如果某个未知顶层键的值看起来像令牌映射(含 Hex 颜色、CSS 尺寸等"令牌样"叶子值),而它又不属于受支持的导出 schema,就警告它"会被export命令静默忽略",建议改名或移入受支持的区块——例如base_colors: { light: { ink: "#0B0F14" } }这种值会被识别出来。两条规则合在一起的效果是:格式开放,但不糊涂。
此外,packages/cli/src/linter/spec-config.ts 中定义了两个与"开放性"配套的保护性限制:MAX_TOKEN_NESTING_DEPTH = 20(令牌最大嵌套深度)与MAX_REFERENCE_DEPTH = 10(引用最大解析深度),防止恶意或病态结构导致解析器栈溢出——开放不等于无防护。
"Tokens are context" 在规范层的确证
回到 docs/spec.md 的 "Consumer Behavior for Unknown Content" 一节,可以看到 linter 与解析器对未知内容的既定态度:
| 场景 | 行为 |
|---|---|
未知小节标题(如## Iconography) | 保留,不报错 |
| 未知颜色令牌名(值合法即可) | 接受 |
| 未知排印令牌名 | 作为合法排印接受 |
| 未知间距值 | 接受;若非合法尺寸则以字符串存储 |
未知组件属性(如borderColor) | 接受但给出警告 |
| 重复小节标题 | 报错,拒绝该文件 |
这张表是对"格式通过用户生长"的最直接背书:除"重复小节"这一结构性错误外,其余一切未知内容都以"保留 + 接受"的方式温和对待。而整套 11 条 lint 规则的完整清单与各自严重级别,记录在 packages/cli/src/linter/linter/rules/index.ts(DEFAULT_RULE_DESCRIPTORS)与 README.md 的 Linting Rules 一节中——从broken-ref(断引用,error)到omitted-rules(omitted配置校验,info),每一条规则的职责边界都被明确固定。
给 Agent 与写作实践者的可操作结论
综合原文档的哲学论述与仓库的工程实现,可以提炼出四条落地准则:
- 先写叙述,再补令牌。用一句具体参照锚定整个设计("像 1970 年代老牌大学的讲义"),然后用
{tokens}引用把精确值挂到叙述上;参照越具体,负面约束越免费。 - 形容词只描述区域,参照物才描述点。"现代、干净、可信"必然产出平庸的中间值;"像一份印刷的学术讲义"才能把设计推向精确的位置。
- 用有意的 Do's and Don'ts 收尾,而不是流水账。正面参照负责定调,负面清单负责守住边界,二者配合才是"sweet spot";如果禁令列表长得失控,优先回头打磨正面描述。
- 放心扩展格式,但不制造困惑。
motion、iconography、elevation等自定义键完全合法,linter 会静默接受;唯一要注意的是别把自定义键拼写成近似内置键(会触发unknown-key警告),也别让令牌样值的自定义键被export静默丢弃(token-like-ignored会提醒你)。
最终回到原文档的收尾语:"这份文档传达的是 DESIGN.md 的叙事与哲学,以厘清它解决什么问题、以及目前如何尝试解决这些问题。"这份哲学不是理论空谈——它被完整地编码进了规范(docs/spec.md)、规范配置(packages/cli/src/linter/spec-config.yaml)、lint 规则实现与三个实战示例(examples/atmospheric-glass/DESIGN.md、examples/paws-and-paths/DESIGN.md、examples/totality-festival/DESIGN.md)之中。理解这套哲学,你就理解了为什么一份 DESIGN.md 的价值不在它的十六进制值,而在于那些让数值活起来的句子。
【免费下载链接】design.mdA format specification for describing a visual identity to coding agents. DESIGN.md gives agents a persistent, structured understanding of a design system.项目地址: https://gitcode.com/GitHub_Trending/de/design.md
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考