Communitygithub.com

设计系统构建器

从零构建全新的设计系统,或对现有系统进行审计与演进。触发词包括“设计系统”、“组件库”、“设计令牌”、“样式指南”、“审计我的设计系统”、“比较设计系统”、“强化我的设计系统”,或当要求构建跨项目的可复用 UI 基础设施时触发。

设计系统构建器 是什么?

该技能提供了一个结构化的、基于真实事故驱动的设计系统构建方法,涵盖从零构建全新系统到审计、演进现有系统的完整流程。其核心哲学是,任何仅存在于注释或文档中的规则最终都会被意外违反,且违反行为不会产生错误,因此每个阶段都以“如何自动强制执行”的问题结束。技能分为八个阶段:发现(通过具体示例明确需求)、作者令牌(以原始通道三元组定义颜色,确保可组合性和可维护性)、构建原语(组件严格引用令牌,避免硬编码变体和可访问性问题)、守护(编写零依赖的检查脚本,确保所有规则可自动化验证,并强调检查脚本必须能在消费者项目中运行)、文档(按受众拆分哲学、操作规则和决策日志,并维护一个导入真实包的展示示例)、打包分发(作为可安装包发布,正确处理 Tailwind 预设的 content 数组合并问题)、验证(针对全新安装测试,区分类型检查和真实渲染验证)。技能还提供了详细的 bug 目录作为参考,每个规则背后都有一个真实的上线 bug,确保每个指导都有实际依据。支持 Claude Code、Cursor、Codex 等 AI 代理平台,适用于希望构建高质量、可维护设计系统的开发团队。

兼容平台~Claude Code~Codex CLI✓Cursor
npx skills add https://github.com/rodney-hu/skills/tree/HEAD/skills/design-system-builder

在你喜欢的 AI 中提问

打开一个已预加载此 Agent Skill 的新对话。

文档

设计系统构建器 是做什么的?

这不是一份通用的最佳实践清单。下面的每条规则之所以存在,是因为跳过它曾导致真实的上线 bug——其中大多数是静默的(没有错误,未构建失败,只是在浏览器中表现异常)。references/bug-catalog.md 记录了每个规则背后的具体事件。在开始构建一个足够重要的系统之前,请先阅读一遍。

贯穿始终的核心准则:一条仅存在于注释或文档中的规则,最终一定会被偶然违反,而且违反行为不会产生任何错误。 下面的每个阶段都以一个问题结束:“如何自动强制执行?”——如果答案是“靠人记住”,那么这个阶段还没完成。

两个入口点:

  • 构建新系统 → 按顺序执行阶段 1–7。
  • 审计或演进现有系统 → 阶段 8,它会按需跳转到阶段 1–7。

阶段 1 — 发现

在构建之前先提问;在此阶段进行猜测会产生没人想要的系统,而当这个问题变得可见时,纠正成本已经很高昂。

  • 参考材料应该是具体的示例,而不是形容词。 “现代且干净”无法明确任何东西;三张截图则能做到。
  • 颜色承载含义,还是仅用于美观? 如果调色板包含分类值(元素、状态、品牌线),请提前决定每个值是否也是一种行为(动效、形状、重量)还是纯粹视觉的。为追求美观而选择的颜色,事后为其附加含义,比一开始就决定要困难得多。
  • 内容放在什么上面? 不要假设页面背景和面板背景“足够接近”。在确定使用之前,测量候选组合——参见阶段 2.3。
  • 范围:全新构建、优化现有系统,还是与先前/同级系统比较?如果不是全新的构建,请路由到阶段 8。
  • 分发:单个项目还是跨多个项目复用?这个答案决定了阶段 6(打包)是否适用。如果回答错误,你可能会对一个一次性项目过度工程化,或对一个即将在三个仓库中漂移的项目工程化不足。

阶段 2 — 作者令牌

按优先级排序——这些搞错了,那么基于它们构建的一切都会继承错误的基础。

2.1 — 颜色只编写一次,作为原始通道三元组,永远不要使用裸十六进制。

--fire-rgb: 170 0 0;                          /* 唯一编写的值 */
--fire: rgb(var(--fire-rgb));                 /* 派生值 */
--fire-soft: rgb(var(--fire-rgb) / 0.18);     /* 派生值 */

这并非风格问题。Tailwind(以及大多数实用工具系统)的不透明度修饰符(bg-fire/40)无法将 Alpha 通道组合到不透明的 var(--x) 十六进制值上——该类会静默解析为空,没有任何错误或警告。正是这个 bug 启动了这个模式所源自的强化过程。每种颜色只编写一个数字,也意味着永远只有一个地方需要修改它。

2.2 — 每种可以渲染为文本的颜色都会有一个 -text 变体,并且字形(glyph)应使用该变体,而不是基础颜色。 通过计算 WCAG 相对亮度(公式在 assets/check-tokens.template.mjs 中)来验证对比度,而不是凭肉眼判断。针对一种表面通过 4.5:1 的色调可能无法通过另一种表面的检查——这正是阶段 2.3 重要的原因。

2.3 — 如果有多个表面层级(页面/面板/凹陷),将其定义为真实的层级比例,并在确定之前测量层级之间的间隔。 “稍微亮一点”不是决定,而是猜测。一个真实系统在发布后,整个高度模型被反转(ink-as-page → void-as-page),原因仅仅是因为有人最终计算了实际的合成对比度:一个方向是 1.04:1,另一个方向是 1.19:1。在基于该选择进行构建之前进行这种计算,而不是在利益相关者说“卡片感觉太平”之后。

一旦有了比例,每个文本安全令牌都需要对照它可以出现的每个层级检查对比度,而不仅仅是它设计的那个层级。

2.4 — 动效:一个缓动曲线,一个小的固定持续时间集合。 如果有任何 JS 动画(framer-motion、GSAP、手动 requestAnimationFrame)将使用这些值,请将曲线和持续时间同时导出为 JS 常量和 CSS 常量——CSS 变量对 JavaScript 不可见,而手动输入的贝塞尔曲线或持续时间会立即且不可见地漂移。保持 CSS 和 JS 版本相互检查(阶段 4)。

2.5 — 任何类似发光/渐变效果的数值上限必须是实数,并根据实际发布的值进行检查——包括渐变停止点,而不仅仅是单一 Alpha 颜色的平面值。 “微妙发光”如果没有强制上限,就会导致全页面环境光效果逐组件地恢复,而每个组件单独看起来都是合理的。

2.6 — 每个令牌都获得一行注释,说明何时使用它,而不仅仅是它的渲染结果。

阶段 3 — 构建原语

  • 组件引用令牌。没有十六进制,没有任意值,没有 white/10 风格的字面量——这些正是阶段 4 的防护检查的目标,现在编写它们只会导致稍后检查失败。
  • 任何不透明度修饰符的使用(bg-x/10)必须针对根据 2.1 可组合 Alpha 的令牌——可以检查,所以检查它,不要用眼睛看。
  • 如果一个 prop 旨在覆盖变体,请不要将变体硬编码到组件的基础类中。 在每个卡片中烘焙 className="shadow-lg",并提供一个旨在替换为 shadow-root-earth 的 prop,会产生两个具有相同特异性的 box-shadow 实用工具——胜者由生成的样式表中的源顺序决定,并且是静默的。使变体成为一个以 prop 键控的查找表(Record<Variant, string>),永远不要使用基础类加预期覆盖。
  • 样式 prop 不能替代真正的交互元素。 一个通过 interactive prop 看起来可点击(cursor-pointer、悬停抬起)但实际上是 <div> 的卡片,为鼠标用户提供了一个键盘用户永远得不到的便利——没有焦点环,没有角色,没有激活。如果某个东西需要点击,请渲染 <a> 或 <button>(一个 CardLink/CardButton 对偶),而不是在被动版本上添加样式切换。
  • 对于真正复杂的交互组件(模态框、标签页、提示),依赖平台。 原生 <dialog> 免费提供了焦点陷阱和按 Escape 关闭的功能;手写的实现通常会搞错其中一项。标签页的 Roving tabindex 是手写标签页实现中最常见的错误(每个非活动标签页仍然是一个 Tab 停靠点)——明确验证它(tabindex 在箭头键导航时按 [0,-1,-1,-1] → [-1,0,-1,-1] 移动),不要假设。
  • 如果系统需要自定义图标/标记资产,提前确定绘制规则(网格、描边粗细、端点样式),并以与颜色规则相同的方式强制执行它们——这也是阶段 4 的任务。

阶段 4 — 守护它

编写一个小型、最好零依赖的检查脚本。这是整个过程中杠杆率最高的工件——references/bug-catalog.md 中的每个 bug 都是一个只存在于散文中的规则,直到有人将其编码到这里。

最小覆盖范围(调整 assets/check-tokens.template.mjs):

  • 组件或预设引用的每个令牌都存在。
  • 组件源中没有手动输入的十六进制或字面量不透明度颜色。
  • 每个文本安全令牌在它可以出现的每个表面层级上的 WCAG 对比度(2.3)。
  • 文档中记录的数值上限作为实际发布值的数字强制执行——包括渐变的最亮停止点,而不仅仅是简单颜色(2.5)。
  • JS 运动常量与其 CSS 令牌对应项匹配(2.4)。
  • 容易被意外违反的结构规则——例如,“没有全页面背景发光”:检查发光实用工具是否从未应用于页面级元素,而不仅仅是令牌的 Alpha 值被限制。
  • 将任何可能匹配普通散文的规则(例如,关于“第二个缓动曲线”的规则)的范围限定为实际代码上下文——类属性、CSS 值——而不是任意文本。标记自己文档的 linter 会被禁用,这违背了目的。

检查最终必须针对消费项目运行,而不仅仅是系统自己的仓库。 这是这里可学到的最大教训:在一个真实系统中,每个实际发生的规则违反——11 个小于 24px 的字体大小、一个站点上三个不同的 <h2> 大小、五个竞争的字母间距值、第二个缓动曲线、发明的持续时间——都发生在消费该系统的一个项目中,而内部检查从未查看过那里。很长一段时间以来,它只守护着从未违反过规则的代码。发布检查器,使其可针对任意源代码运行(npx your-check ./src),并为真正的例外提供一个作用域化的逃生口(your-check-allow: <rule> — <reason> 注释约定),而不是让人们禁用整个检查。

在信任之前,对每个检查进行负面测试。 在草稿副本中故意违反每条规则,并确认脚本实际失败了它。从未被观察到失败的检查是未经验证的——你不知道它是工作还是静默无效。

阶段 5 — 文档

三个文件,按受众拆分——抵制添加第四个的冲动。第四个文档(聚合的“design.md”、第二个样式指南文档)是同一组值可能漂移的第二个地方;三个各自拥有不同问题(为什么/该做什么/决定了什么)的文档就足够了。

  • philosophy.md — 为什么、身份、选择背后的含义。读一次,很少重读。
  • foundations.md — 操作规则。某人在构建新内容之前检查的内容。应该与阶段 4 的守护强制执行的内容大致一一对应——如果一条规则在一个文件中而不在另一个文件中,请选择缺少的那个并修复它。
  • decisions.md — 运行日志,最新的条目在顶部,记录具体的判断及其原因。永远不要删除被取代的条目。 将其标记为已取代,并解释发生了什么变化以及为什么。在没有书面记录的情况下撤销一个真实的决定(高度模型、主次层次结构),会迫使下一个人(通常是未来的你)从头推导原因,或者更糟糕地,静默地重新破坏原始决定修复的内容。
  • 一个展示真实令牌和组件的活示例 — 而不是对它们的描述。如果它不能实际 import 包(例如一个静态的单文件 HTML 文档),那么它携带的是每个值的第二个内联副本,并且该副本会漂移:一个真实系统的展示示例在有人将其重建为导入实际包的东西之前,静默地落后于自己的令牌两个版本。如果静态展示不可避免,最快的修复是阶段 4 保护内联副本免受真实源代码的影响——但计划在系统稳定到有真正消费者后尽快淘汰它。
  • 如果展示示例是可部署的,请将其部署到真实的 URL,并在真实、狭窄的视口下查看。 一个布局错误,只有当特定部分比其内容窄时才会出现(比如说,一个故意溢出的装饰元素使整个文档变宽),对于任何静态检查都是不可见的——不是令牌违反,不是对比度失败,不是 lint 错误。它只存在于实际浏览器中的实际宽度。有一个这样的 bug 发布并存活了三个版本,因为唯一查看该系统的地方是一个本地文件。
  • 一旦有多个文档,请写下一个版本控制策略以及阅读文档的顺序。花费一段文字,节省某人从头推导的时间。

阶段 6 — 打包和分发

仅当阶段 1 说“跨项目复用”时。

  • 作为可安装包发布(带有 exports 映射的 package.json)。永远不要“将这些文件复制到你的项目中”——复制没有路径让下游修复流回上游,这就是系统的真实来源悄悄地变成两个副本中更破损的那个的原因。这正是整个谱系中第一个 bug 的根本原因。
  • 如果分发 Tailwind preset:preset 的 content 数组不会由 Tailwind 合并。 消费者自己的 content 数组会完全替换 preset 的。这是目录中第二昂贵的 bug,而“明显”的修复——在 preset 中声明 glob 并假设它们会组合——不起作用;构建会返回字节相同的输出,这就是标志。preset 必须导出其 globs,并且每个入职文档(README、脚手架命令如果存在)必须展示消费者显式地展开它们:content: [...preset.content, ...yourGlobs]。使用打包器的配置解析 API 验证这一点,不要假设——在“修复”后构建工件未更改是 bug 信号,而不是巧合。
  • 使用标签进行版本控制,而不仅仅是分支。 分支锁定会在源仓库的分支前进时静默地在消费者下移动——这正是消费者可以发布未经测试的版本而无需任何人决定的原因。如果消费者需要移动到新的标签,请记住 lockfiles 可以锁定确切的已解析提交,独立于 package.json 中标签的内容——强制重新解析并验证 node_modules 实际报告了新版本,然后再信任重建。
  • 如果包为新消费者提供脚手架/init 命令,请从一开始就发出正确的、展开的内容配置——不要依赖每个消费者的 README 阅读纪律来处理一个严重到足以拥有自己的决策日志条目的陷阱。

阶段 7 — 验证

持续应用,而不仅仅在最后应用一次。

  • 针对全新的安装或克隆进行测试,而不是工作目录。 工作目录隐藏了缺失的导出、未提交的修复和过时的缓存,这些是真实消费者在第一天就会遇到的。
  • 在“修复”后构建工件未更改是修复未起作用的信号,而不是巧合。 在相信任何变化之前,先对实际输出进行差异比较。
  • 类型检查不是验证。 这个目录中最严重的 bug——字体以完全错误的字型渲染——通过了所有可用的静态检查,只有在真实消费者中渲染组件并查看时才被发现。在完成之前,至少在真实环境中渲染每个内容一次。
  • 对看起来方便的验证平台 API 保持怀疑。 document.fonts.check() 看起来像是“此字体是否实际包含此字形”的完美工具,但会撒谎(它报告可用的内容...

...(内容截断)

Individual skills in this repo

This repo contains 1 individual skill — each has its own dedicated page.

相关技能