emilkowalski/skills 深度解析:如何将设计审美嵌入 AI Agent 的代码基因
近期,各类 AI 编程工具纷纷推出 Skills 功能,但多数方案仍聚焦于工程流程优化——自动编写测试、执行代码审查、辅助版本发布。而 emilkowalski/skills 则另辟蹊径,它瞄准了前端开发中最难用提示词说清楚的那个领域:界面设计品味。

作者 Emil Kowalski 曾在 Vercel 和 Linear 担任设计工程角色,并维护过 Sonner 这类极其注重用户手感的开源组件。截至 2026 年 7 月 2 日,该仓库在 GitHub 上已收获 4.1k 星标,采用 MIT 协议。README 中的安装指令极为简洁直接:
npx skills@latest add emilkowalski/skills
用一句话来概括:它将设计工程师对 UI 细节的判断方法,转化成了 AI Agent 可遵循的规则体系。
一、它解决的核心问题:Agent 能写出界面,但未必能做出对的细节
如今让 Agent 生成一个弹层、下拉菜单、Toast 通知或按钮状态,通常并不困难。真正的难点在于,它常常会做出一些“功能可用但体验欠佳”的选择:
- 下拉菜单入场时使用了
ease-in,导致首帧响应迟钝; - 弹层从
scale(0)出现,像是在屏幕上凭空冒出来; - 对 hover、键盘快捷键、命令面板这类高频交互也添加了动画;
- 滥用
transition: all,把布局等不该动的属性也纳入动效; - 弹出层从中心缩放,而非从触发按钮的位置自然展开;
- 动画时长设置过长,让产品整体显得笨重迟钝;
- 忽略
prefers-reduced-motion和触屏设备上的 hover 限制。
这些问题单独看都微不足道。用户可能说不出具体哪里不对,但直观感觉就是界面不够流畅、不够迅捷、不够高级。
Emil 的判断是:Agent 缺少的并非 CSS 语法知识,而是设计品味(taste)。更准确地说,它缺乏一组经过训练、能落实到代码细节中的设计决策机制。
二、仓库只有三项技能,但分工清晰明确
这个仓库结构精简,根目录下仅包含三个技能模块:
| 技能 | 用途 |
|---|---|
emil-design-eng | 主技能,覆盖动效、组件设计、手势交互、性能优化、无障碍设计以及组件默认值设定 |
review-animations | 专门审查动画与运动代码,默认采用严格模式,必须输出 Before / After / Why 对比表格 |
animation-vocabulary | 将模糊的动画描述转化为精准术语,便于与 AI 或设计师高效沟通 |
emil-design-eng 是核心主体。它不是一份泛泛的“设计建议清单”,而更像设计工程师的判断系统:何时需要动效、为何需要动效、采用何种缓动曲线、持续多长、是否可中断、是否会导致掉帧、是否兼顾减弱动态效果的设置。
review-animations 则将上述判断固化为审查流程。它定义了十条不可妥协的标准:动画必须具有明确目的,高频操作不应使用动效,UI 动画时长尽量低于 300ms,只对 transform 和 opacity 做动画,弹出层应从触发点缩放,移动效果需要支持 reduced motion。它的态度非常强硬:通过审查是努力争取来的,而非默认状态。
animation-vocabulary 看起来最轻量,但极为实用。很多人并非不会描述动效,而是不知道那个现象的专业名称。比如“iOS 里拖到底会有阻尼然后弹回来的感觉”,技能将其归类为 rubber-banding;“列表项一个接一个顺序出现”,则对应 stagger。术语一旦准确,提示词和评审质量都会大幅提升。
三、这个技能具体如何落地使用
首先进行安装:
npx skills@latest add emilkowalski/skills
安装完成后,它会被当作一组供 Agent 读取的技能文件,而非你需要手动执行的 npm 包。在 Claude Code、Codex、Cursor 等工具中进行 UI 相关任务时,可以直接引用对应的技能。
最常见的应用场景有三种。
1. 实现组件时,先按设计工程规则执行
例如,你在编写一个 Vue 版本的 Popover,可以这样向 Agent 下达指令:
请使用 emil-design-eng 优化 src/components/BasePopover.vue。具体要求:打开时从触发按钮的位置自然出现,动画要轻量,不影响键盘操作,支持 prefers-reduced-motion。
这个提示词远比“让动画更自然”有效得多。它直接明确了组件、交互目标、触发源、性能与无障碍约束。emil-design-eng 会将这些要求转化为具体实现,例如设置 transform-origin、使用 ease-out、从 scale(0.96) 开始、配合 opacity 变化,并添加 @media (prefers-reduced-motion: reduce) 规则。
2. 编写完成后,用 review-animations 进行严格评审
你也可先不做修改,而是让 Agent 先做审查:
请使用 review-animations 审查 src/components/BaseDropdown.vue 中的动画,仅关注 motion 相关问题。按 Before / After / Why 表格输出,不要修改代码。
这个技能特别适合放在 PR 之前的检查环节。它不会笼统地说“动画可以优化”,而是会抓出具体问题,比如:
| Before | After | Why |
|---|---|---|
transition: all 300ms ease-in | transition: transform 180ms cubic-bezier(0.23, 1, 0.32, 1), opacity 180ms cubic-bezier(0.23, 1, 0.32, 1) | all 会误伤其他属性,ease-in 会让进入动画开头变慢 |
transform: scale(0) | transform: scale(0.96); opacity: 0 | 弹层不应从不存在的状态突然出现 |
transform-origin: center | transform-origin: var(--popover-transform-origin) | Popover 应该从触发按钮的位置向外生长 |
你可以先查看评审表,再决定让 Agent 修改哪些具体项。
3. 无法准确描述动效名称时,使用 animation-vocabulary 查询术语
很多时候不是你不会做,而是不知道如何精确描述。例如你可以这样询问:
请使用 animation-vocabulary:iOS 那种拖到边界会产生阻力,松手后弹性回弹的效果叫什么?
它会给出 Rubber-banding。再比如:
一个列表中的卡片不是同时出现,而是一个接一个淡入上移,这叫什么?
它会给出 Stagger。这个技能尤其适合在需求写作、提示词编写、与设计师对齐术语时使用。
四、实际案例一:优化 Popover 动画
假设 Agent 最初为你生成了这样一段 Vue transition:
.popover-enter-active,.popover-lea ve-active {transition: all 300ms ease-in;}.popover-enter-from,.popover-lea ve-to {opacity: 0;transform: scale(0);}
这段代码功能上可行,但交互手感欠佳。主要存在三个问题:transition: all 过于粗放,ease-in 导致进场动画开头卡顿,scale(0) 使弹层像凭空闪现。
经过 review-animations 审查后,更合理的版本应该如下:
.popover {transform-origin: var(--popover-transform-origin, top center);}.popover-enter-active,.popover-lea ve-active {transition:transform 180ms cubic-bezier(0.23, 1, 0.32, 1),opacity 180ms cubic-bezier(0.23, 1, 0.32, 1);}.popover-enter-from,.popover-lea ve-to {opacity: 0;transform: scale(0.96);}@media (prefers-reduced-motion: reduce) {.popover-enter-active,.popover-lea ve-active {transition: opacity 120ms ease;}.popover-enter-from,.popover-lea ve-to {transform: none;}}
这里所做的改进并非“增加高级感”,每一项改动都有明确的设计理由:
- 仅对
transform和opacity做动画,避免触发布局重排和绘制; - 进入动画选用更强烈的
ease-out,确保用户点击后能立刻获得反馈; - 从
scale(0.96)起始,保留一定体积感,避免突然消失感; transform-origin交由组件根据触发按钮位置动态设定;- 在 reduced motion 模式下保留淡入淡出,移除位移效果。
这类案例尤其适用于组件库开发。Popover、Dropdown、Tooltip、ContextMenu 等组件都可以复用同一套审查逻辑。
五、实际案例二:为什么 Toast 不建议随便使用 keyframes
Toast 看似简单,但它是极易被高频触发的组件。比如用户连续保存、连续复制、连续报错,Toast 会频繁地加入和移除。
一种常见写法如下:
.toast {animation: toast-in 400ms ease-in-out;}@keyframes toast-in {from {opacity: 0;transform: translateY(24px);}to {opacity: 1;transform: translateY(0);}}
问题在于 keyframes 不适合需要频繁中断和重新定位的 UI。当新的 Toast 插入、旧的 Toast 上移、用户鼠标悬停暂停、滑动关闭等场景发生时,动画需要能够从当前状态继续调整,而不是每次都从零开始播放。
emil-design-eng 更倾向于使用 transition 或 spring 物理动画:
.toast {opacity: 1;transform: translateY(0);transition:transform 220ms cubic-bezier(0.23, 1, 0.32, 1),opacity 180ms ease;}.toast[data-entering],.toast[data-lea ving] {opacity: 0;transform: translateY(12px);}.toast:active {transform: scale(0.98);}
如果是可拖拽关闭的 Toast,它还会提醒你关注速度而非仅仅距离。用户快速一甩,即使没有拖过一半距离,也应该能够触发关闭——这就是技能中所提到的 momentum dismissal 动效理念。
关键区别就在这里:它不仅是给出了一组动画参数,更是考虑了这个组件在真实产品中会被如何使用。
六、实际案例三:按钮、Tooltip 与高频操作的动效处理
有些动效应该添加,有些则应该删除。这个技能最具价值的地方之一,就是帮助你判断交互频率。
按钮按下的反馈适合加动效,因为它是用户直接操作后的即时反馈:
.button {transition: transform 140ms cubic-bezier(0.23, 1, 0.32, 1);}.button:active {transform: scale(0.97);}
Tooltip 的逻辑则更为细致。第一次 hover 可以加入延迟,避免用户路过时误触发;但当某个 tooltip 已经打开,用户移动到相邻按钮时,后续 tooltip 应该立刻出现,无需每个都等待一遍。
请使用 emil-design-eng 检查 Toolbar 的 tooltip 体验:第一次 hover 延迟打开,后续相邻 tooltip 立即响应,触屏设备不要触发 hover 动画。
相反,命令面板、快捷键触发、键盘上下移动列表等高频操作,通常应该完全移除动画。用户一天可能打开命令面板几十次甚至上百次,此时每多 150ms 的延迟都会变成阻力。
这条规则对后台产品尤其实用。SaaS、CRM、开发工具、管理系统中的 UI 不需要处处有动效,真正重要的是响应速度、状态清晰、不打断连续操作。
七、最核心的方法:将品味拆解为可执行的决策树
这个项目最值得学习的地方,不在于某个具体的动画参数,而在于它把“感觉对不对”这一主观判断,拆解成了可执行的工程化决策。
比如,判断动画是否应该出现,它首先抛出一个非常工程化的问题:这个动画用户一天会看到多少次?
- 键盘快捷键、命令面板这类一天可能触发上百次的操作——不做动画;
- hover、列表导航这类一天几十次的操作——删除或大幅减轻动效;
- 弹窗、抽屉、Toast 这类偶尔出现的界面——可以做标准动画;
- 首次引导、庆祝反馈、营销演示这类低频场景——可以适度增加愉悦感。
这比单纯说“动画要克制”有用得多。“克制”是一种态度,而“频率表”是具体的执行规则。Agent 拿到后不需要猜测,只需判断场景属于哪一类。
缓动方式也采用了同样的处理逻辑:
- 元素进入或退出时,使用
ease-out,因为一开始就要给用户反馈; - 屏幕上已有元素移动或变形,使用
ease-in-out; - hover 或颜色变化,使用普通
ease; - 跑马灯、进度条这类匀速运动,才用
linear; - UI 交互中避免使用
ease-in,因为它开头慢,用户最关注那一刻反而没有反应。
再比如 scale(0)。很多 Agent 会把弹层初始状态写成完全缩小到 0,这在代码层面很自然,但在视觉上很不自然。Emil 的规则是从 scale(0.9) 到 scale(0.97) 这类仍有体积感的状态开始,再配合透明度变化。原因很直观:现实世界中的物体不会从完全不存在突然变出来。
这就是所谓“品味程序化”的关键:将审美从玄学拉回具体判断,不断追问“为什么这个选择感觉更好”,然后将答案写成规则、阈值、反例和输出格式。
八、它对前端开发最有价值的点
前端开发中有很多判断是类型系统无法兜底的。按钮有没有按压反馈,tooltip 第二次 hover 是否应该跳过延迟,drawer 拖拽越界时是硬停还是加阻尼,toast 堆叠时用 keyframes 还是 transition——这些都不会导致构建失败,但会决定产品的使用手感。
emilkowalski/skills 对前端开发的价值在于,它将这些隐性判断融入了 Agent 的默认工作流。
当你让 Agent 修改一个 popover 动画时,它不只是“让它更顺滑”,而是会检查:
- 这个交互频率高不高;
- 动画是否有明确的目的;
- 时长是否超过 UI 动画的合理范围;
- 是否使用了
transition: all; - 是否从触发源位置缩放;
- 是否只动了 GPU 友好的属性;
- 是否支持 reduced motion;
- 触屏设备上 hover 是否会误触发。
这套检查对组件库、SaaS 后台、设计系统、动效密集型前端项目都很有用。它特别适合那些“功能已经实现完毕,但总觉得不够像一个成熟产品”的阶段。
九、它比普通提示词更具体
普通提示词经常写成这样:
让动画更自然一些,注意性能。
这类话对人类或许有启发作用,但对 Agent 远远不够。因为它既不知道“自然”是什么,也不知道“注意性能”具体要检查哪些属性。
这个仓库的写法更像是一份代码审查标准:
- 先定义适用场景;
- 再定义判断顺序;
- 给出具体阈值,比如 100 到 160ms、150 到 250ms、UI 动画尽量低于 300ms;
- 给出危险信号,比如
transition: all、scale(0)、ease-in、布局属性动画; - 给出修复优先级,能删就删,不能删再减弱,再改缓动、改 origin、改性能;
- 强制输出 Before / After / Why 表格,让评审结果可读、可执行。
这也是它和很多“AI 设计建议”最大的区别。它不靠一句“提升品味”,而是把品味拆解成一组不会轻易走样的检查项。
十、这个项目也存在边界
第一,它带有很强的作者风格。Emil 的动效观偏向高质感、克制、响应快、少装饰。这对大多数产品界面是好事,但并不代表所有品牌都应该照搬。游戏、儿童产品、创意展示站、强品牌营销页,可能需要更强的表现力和个性。
第二,它主要覆盖设计工程和 motion 范畴,并不替代完整的产品设计流程。信息架构、业务流程、可用性研究、视觉品牌系统,仍然需要额外的上下文支持。
第三,它要求 Agent 真正具备遵循长规则的能力。能力较弱的模型可能会读完规则,但在实现时仍然回到默认写法。这个问题不只属于这个仓库,所有 Skills 都会遇到。
第四,技能不能替代设计师。它能把已知经验稳定复用,但新的判断、复杂取舍、品牌方向,仍然需要人类来做最终决策。
十一、我会如何实际使用它
如果你是前端开发者,建议不要把它当成“装了就自动变高级”的魔法包,而是视为一个可复用的 UI 评审助手。
比较适合的用法有三种。
第一种,用它审查组件库中的动效。比如 Dialog、Popover、Dropdown、Toast、Tooltip、Drawer——这些组件一旦细节定下来,会被整个产品反复复用,值得提前把手感调整到位。你可以先让 review-animations 只输出评审表,再挑选其中几项让 Agent 修改。
第二种,用它给 Agent 补充默认的品味。让 Agent 实现交互前,先加载 emil-design-eng;实现完后用 review-animations 再审一遍。比如“给 BaseDropdown 加动效”这类任务,不要只说“顺滑一点”,而是把频率、触发方式、reduced motion、键盘操作都写进去。
第三种,把它当成自己编写技能的范本。你可以照着它的结构,把团队自身的设计规范写成类似的规则:表单错误如何出现,空状态如何处理,数据表格如何加载,按钮 loading 状态如何变形,哪些场景禁止加动效。比如团队规定所有后台弹窗都不做 bounce,所有列表加载用 skeleton 而非 shimmer;这些都可以写成可复用的技能文件。
十二、为什么这个项目值得分享
emilkowalski/skills 讨论的不是“AI 会不会设计”这种宏大问题,它关心的是一个更实际的问题:
如果你已经知道什么是好的设计,那么能否把这些判断交给 Agent 稳定地执行?
这个问题对前端尤其重要。因为前端的质量差异,往往不在功能层面,而在无数细节层面。一个按钮按下去有没有物理反馈,一个菜单从哪里长出来,一个动画是不是慢了 100ms,一个拖拽越界是否有阻尼——这些决定了产品是“能用”,还是“有手感”。
Emil 的答案很直接:品味可以训练,也可以表达。表达清楚之后,就可以变成可执行的技能。
这也是这个仓库最值得借鉴的地方。它提醒我们,未来编写 Skills 不只是把流程交给 Agent,也可以把专业判断交给 Agent。前者让 AI 少犯工程错误,后者让 AI 更接近一个靠谱的同行。
参考资料
- GitHub 仓库:github.com/emilkowalsk…
- 项目主页:emilkowal.ski/skill
- Agents with Taste:emilkowal.ski/ui/agents-w…
- Developing Taste:emilkowal.ski/ui/developi…
- animations.dev:animations.dev/
