component-from-design:不许凭感觉抄设计稿,先把数值抄下来
这是 skill 深挖系列的一篇,属于通用组——不绑定某个阶段、在日常活里随手能用的那一组。母篇是《从能跑到可交付:一套前端 AI 协作系统的设计笔记
》。这一篇只钻一份 skill:component-from-design,管的是「照着一份设计规格、设计稿或截图做组件」这件天天发生的事。我用的系统是开源的 frontend-harness-cli
,这份 skill 在仓库的 templates/skills/component-from-design/ 里,下面引的都是它的原文规则。
认得出是它,但每一处都不对
先看一个具体的场景。
你把一张卡片组件的设计稿发给 AI,让它照着做。半小时后它交活,你打开页面:第一眼,认得出,就是那个卡片。结构对,层级对,字也都在该在的地方。
然后你开始逐项量。设计稿里标题和正文之间是 12px,它写了 10px;卡片内边距设计稿是 16px,它写了 14px;主按钮的蓝,设计稿里只有一个,它用了三个——标题一处、按钮一处、链接一处,肉眼看都是「蓝」,取色器一量,三个 hex 值。
每一处单看,都能用一句话打发过去:差 2px,看不出来;颜色接近,用户分不清。但加总起来,这个页面和设计稿之间隔着几十处「看不出来」。更麻烦的是它们会累积:下一个组件又在它的基础上再偏一点,三个月后整个产品和设计系统已经是两个东西了,只是谁也说不清从哪一页开始的。
这个 bug 出生在哪?不在某一行代码里,在做事的顺序里:它先写了代码,数值是写的时候凭感觉填的。 颜色是它「记得」的蓝,间距是它「觉得差不多」的数,字号是从训练数据里捞的。感觉没有刻度,所以每一处都差一点。
这份 skill 是什么:让设计系统成为视觉取值的唯一来源
component-from-design 的自我定位写在开头,一句话:生成的 UI 会漂移——AI 随手抓一个看起来接近的 hex 值、一个感觉差不多的间距、一个凭记忆的字号,每个选择都「差不多」,加起来就是一套跟任何设计稿都对不上的 UI。这个 skill 要做的,是让设计系统成为视觉取值的唯一来源。
注意它约束的不是审美,是取值的出处。它不关心你做的组件好不好看,只关心一件事:代码里的每一个颜色、间距、字号,能不能追溯到设计系统里的一个 token 名。追溯得上,实现就可对账;追溯不上,就是即兴发挥——哪怕这次即兴发挥恰好填对了数值,下次换个组件它还会再填错。
它适用的场合比「从零做组件」宽:按规格、设计稿或截图新建组件;把现有组件重做样式对齐设计系统;评审一遍下来到处「差一点点」的生成 UI。尤其是在本来就有设计 token 的仓库里干活的时候——有 token 还漂移,纯属白漂。
它怎么干:先抄数值,再写代码,最后对账
流程一共六步,顺序是硬的。
第一步,先读 token 源。 打开项目的 token 定义——CSS 变量文件、theme 对象、设计 token 包都行——列出这个组件需要的 token:颜色、间距阶梯、字号、圆角、阴影。产出是一份 token 短清单。这一步还有个关键动作:需要的取值没有对应 token,现在就标记出来,不要做到一半现编。 缺口要在开工时暴露,不要藏进代码里。
第二步,把规格值映射到 token。 写样式之前,把设计稿里的每个视觉值翻译成 token 名,产出一张映射表,形如「卡片内边距 16px -> var(–spacing-4)」。这一步是全流程的枢纽:先翻译,后写码。 翻译不出来的值怎么办?skill 的原话是:映射不上的值是给设计师或用户的问题,不是硬编码的邀请函。也就是说,缺口的正解是提问或上报,不是填一个最像的数然后闭嘴。
第三步,先复用再新建。 动手写新组件之前,先在组件库里找:有没有现成组件或变体能覆盖这个需求?产出是把复用决策记下来——复用了 X,或者新建、理由是 Y。反模式里专门点了一条:组件库明明有现成的按钮、卡片、弹窗,还从头再写一个,理由仅仅是「写比读它的 API 感觉更快」。复用已有的组件,等于复用它已经对好的 token 和状态,比新写的任何实现都更「像设计稿」。
第四步,样式只用 token。 新代码里每个颜色、间距、字号,都引用一个 token 或现成组件的 API。完成判据是可以执行的:对新样式搜一遍裸 hex 和裸 px 值,除设计系统允许的例外(比如它允许的 1px 边框)之外,应该一无所获。 注意这个判据的性质——它不是「看起来对不对」,是一条可以跑的搜索。搜得到,就是没做完,没有商量。
第五步,对着规格自查。 工具允许就渲染截屏比对;不允许,就逐元素走查结构:顺序、层级、状态。产出是一份核过的元素与状态清单——默认、悬停、禁用、空、错误——不一致的已修复或已上报。比什么、怎么比,下一节细说。
第六步,报告缺口。 列出规格里没有 token 的值,以及每个值最后怎么解决的:提议新增 token、经批准用了最接近的 token、还是留作一个标记在案的例外。完成判据的原话是:没有任何一处静默即兴发挥。 缺口不可怕,可怕的是缺口被填平了还没人知道——下一个人读代码时,会把那个即兴数值当成设计系统的正式取值抄走。
设计一:「看起来像」不是判据,对账单才是
为什么这份 skill 对「差不多」这么不留情面?它在反模式里给了最锋利的一条:因为「16px 和 token 的 15px 看不出区别」就用一次性像素值——十处看不出的偏差,正是设计漂移的发生方式。
这句话值得拆开。单处偏差确实看不出,这不是错觉。但「看不出」论证的是这一处的视觉影响,证明不了这一处的做法是对的。用一次性数值,等于宣布「这一处的取值由我临场决定」。临场决定没有办法被第二个人复核,也没有办法在设计系统改版时被统一更新——设计师把主色换了,用 token 的地方全站一起变,那处硬编码的蓝纹丝不动,变成化石。
所以这份 skill 把验收方式整个换掉了:不验收「像不像」,验收「对不对得上账」。账有两本。一本是静态的:新样式全文搜索裸值,搜不到才过。另一本是动态的:逐元素、逐状态拿实现和规格比对,清单打勾或标记上报。「看起来很像」在这两本账面前不是证据——它恰恰是漂移最喜欢的掩护,因为漂移永远以「看起来很像」的样子出现。
设计二:token 是设计稿和代码之间的桥
再往深一层:为什么非得是 token,而不是「把设计稿的数值原样抄进代码」?把 16px 抄成 16px,数值分毫不差,不也对上了吗?
数值抄对了,但抄的是一次性的巧合。设计稿里的 16px 不是一个孤立数字,它是设计系统间距阶梯里的一级,在设计师的概念里它叫「四级间距」,全站所有卡片共用。代码里写 var(--spacing-4),表达的是「我用的是阶梯的第四级」;写 16px,表达的是「这里碰巧是 16」。今天两者渲染结果相同,明天设计师把阶梯整体调密一级,前者自动跟上,后者不动。
还有一层:token 名是设计和工程共享的词汇。评审的时候说「这张卡片的内边距用错了,该是 spacing-4 不是 spacing-3」,双方说的是同一件事;说「该是 16 不是 12」,是在对数字,不是对手艺。skill 第二步要求先产出映射表,本质是先把两种语言对齐:设计稿说数值,代码说 token 名,映射表是翻译稿——翻译这件事必须在写码之前单独做完,边写边译,译错的地方就直接长进代码里了。
设计三:设计稿只画了一个状态,组件要扛住全部状态
最后一条设计细节,藏在反模式的最后一条里:只查默认状态——规格里的悬停、禁用、空状态,恰恰是 token 纪律最先崩的地方。
道理不难懂。设计稿通常画的是默认状态:数据满的、能点的、一切正常的那一帧。AI 照着做,默认状态有稿可依,取值还算收敛;到了悬停色、禁用态的灰、空状态的插图间距,设计稿没画,或者画在另一页没发给它——这时候它就开始即兴了。而即兴恰恰集中在用户最容易感知「这产品糙不糙」的地方:按钮按下去没反馈、列表空的时候一片死白、出错了一行红字没有任何设计。
所以第五步的自查清单把状态逐个列死:默认、悬停、禁用、空、错误,一个不能少。设计稿没画的状态不是不存在,是要在开工时就当成缺口报出来——走第一步和第六步的老路:标记、上报、要么补规格,要么用 token 推导并记录在案。还是那条总原则:可以有缺口,不可以有静默的即兴。
它管不了什么
把边界说清。
第一,它管取值出处,不管设计本身好不好。设计系统的 token 定得难看,照着它做的组件会难看得整整齐齐——这份 skill 保证的是忠实,不是品味。
第二,它依赖 token 源存在且可信。项目压根没有设计 token,或者 token 文件本身就是东拼西凑的历史沉积,这份 skill 的第一步就无从下手——那是先建设计系统的问题,不是做组件的问题。
第三,截屏比对也好、逐元素走查也好,比的是实现和规格的差,判不了规格和真实使用之间的差:设计稿在真机上字太小、对比度不够这类问题,它发现不了。
在 Harness 的分工里,它守的是「实现忠实于规格」这一段。规格里的文案要多语言抽取、组件之外的状态与数据问题,各有别的 skill 管——下一篇讲 i18n-extract:界面上的文字为什么不能直接写死在组件里。