Ahmed UI Craft 技能指南
语言模型倾向于向网络的统计平均值漂移:Inter字体、紫色渐变、三个相同的圆角卡片、每个功能一个表情符号。这种输出一眼就能认出,并告诉用户没有人做出任何决定。此技能强制做出决定,将它们写作为Token,并在交付任何内容之前进行检查。
请按顺序遵循工作流程。每个步骤的存在都是有原因的,跳过它正是导致通用UI产生的原因。
第0步 - 在设计前阅读项目
在创造之前,先观察。按此顺序检查:
- 框架:
package.json(next、react、vue、svelte、astro)或纯.html。 - 样式:
tailwind.config.*或@import "tailwindcss"/@theme(Tailwind v4)、*.module.css、styled-components 或纯 CSS。 - 现有Token:grep 搜索
--color-、--space-、@theme、theme.extend。 - 组件库:
components/ui/+components.json(shadcn/ui)、@mui/material、Radix、Headless UI、Vuetify。
原因:一个漂亮的组件如果忽略了项目现有的Token和库,那么它是一种退化,而不是改进。如果项目已有设计系统,请在其内部工作:扩展其Token,重新设计其原语的样式(对于 shadcn/ui,编辑 globals.css 中的 CSS 变量和组件文件,不要包装它们)。只有在没有系统可扩展时,才引入 assets/tokens.css。
如果没有任何东西存在,则默认为:语义化 HTML + 来自 assets/tokens.css 的 CSS 自定义属性,如果存在 Tailwind,则将其映射到 Tailwind 中。
第1步 - 设计思考(在编写任何代码之前)
在脑海中或在回复中写下四行简短文字:
- 目的:这个屏幕完成什么工作?(例如,“说服团队负责人开始试用”)
- 受众:谁在看,在什么设备上,什么心情?(例如,“运维工程师,桌面端,快速浏览”)
- 基调:三个形容词。(例如,“精确、冷静、密集”)
- 差异点:某人在关闭标签页后会记得的一件事。
差异点必须具体且可见,而不是“干净现代”。好的例子:“价格以巨大的压缩数字显示,贯穿整个边缘”;“每张卡片都有一条细线和像规格表一样的单色索引号”;“主标题是衬线斜体,设置在米白色纸张纹理之上”。
原因:如果你不能命名差异点,输出将是平庸的,因为当没有任何东西将模型拉离平均值时,平均值就是模型产生的结果。
如果用户提供了品牌、截图或现有网站,基调应来自此。不要对已有品牌强加新的方向。
第2步 - 致力于一种美学方向
选择一个方向并在所有地方应用它:字体、颜色、圆角、阴影、动效、图像和文案节奏。混合的方向读起来像是优柔寡断。
| 方向 | 字体 | 颜色 | 形状和深度 | 动效 |
|---|---|---|---|---|
| 编辑风 | 衬线展示字体,人文主义正文字体 | 纸白,墨色,一种强烈的强调色 | 细线,几乎为零圆角,无阴影 | 缓慢淡入,滚动时无动效 |
| 粗野风 | 厚重无衬线或等宽字体,巨大字号 | 原始黑白 + 一种响亮颜色 | 0圆角,厚边框,硬偏移阴影 | 瞬间,爽快 |
| 柔和 / 有机 | 圆润或柔和衬线展示字体 | 暖色调中性色,柔和质朴强调色 | 仅容器大圆角,弥漫阴影 | 有弹性,温和 |
| 工业 / 技术 | 压缩或无衬线 + 等宽字体 | 深中性色,信号强调色(琥珀色、青柠色、青色) | 小圆角,1px边框,网格线可见 | 快速,近似线性 |
| 奢华风 | 高对比度衬线,宽间距大写字母 | 近乎黑色,奶油色,金属或深宝石强调色 | 宽敞空白,细边框 | 缓慢,长缓动 |
| 俏皮风 | 粗壮展示字体,友好看无衬线 | 两种刻意冲突的饱和颜色 | 贴纸形状,偏移阴影 | 有弹性的过冲 |
每一行在 assets/directions/ 中都有一个现成的预设(字体,对比度检查过的浅色和深色调色板,圆角,阴影,动效),以及在 references/directions.md 中有一个标志性动作列表。在 tokens.css 之后加载预设,使用其至少两个标志性动作,并将其色调向品牌偏移。瑞士极简风是第七个预设。
选择的规则:
- 从第1步推导,而不是从习惯出发。面向SRE的仪表板不是“奢华风”。
- 在回复中命名它(“方向:工业 / 技术”),以便用户可以及早更改方向。
- 如果用户要求平淡无奇的东西(“简单,干净”),仍然要选择:“瑞士极简风”是一个方向(严格的网格,一种无衬线字体,黑色 + 一种颜色,零装饰)。
第3步 - 首先定义Token
在编写单个组件之前,为以下内容定义Token:颜色、字体、间距、圆角、阴影、动效、z轴索引。从 assets/tokens.css 开始:保留名称,替换值。
起始值(Fraunces + Instrument Sans,纸色中性色,朱红色强调色)是一个占位符,不是一种样式。在编写组件之前,至少要更改字体配对(来自 references/typography.md,与你的方向匹配)、强调色调以及中性色所偏向的色调。原因:在测试中,不变的起始Token使得一个登录页面和一个定价页面看起来完全相同,即使它们针对的是不相关的产品。这是一个新的默认值,而这正是此技能旨在解决的问题。只有当方向真的是带有红橙色强调色的温暖编辑风时,才保留它们。
获得一个新的、可访问的调色板最快的方法:生成它,然后将其粘贴到第二个块的颜色Token上。
node scripts/palette.mjs --hue 250 --accent 130 --dark-first # 石板色中性色,青柠色强调色
--hue 调整中性色的色调(使用品牌色调),--accent 是强调色的色调。每个文本/背景组合都经过计算,以确保在浅色和深色模式下都通过 WCAG AA 标准,然后才输出。
对于暗色优先的方向(监控仪表板、开发者工具、奢华夜风主题),设置 :root { color-scheme: dark; },这样深色成为默认,浅色则是可选的。
原因:Token使方向可强制执行。组件中硬编码的 #6366f1 或 13px 是一个孤立做出的决定,而二十个这样的决定会使UI漂移。有了Token,深色模式、主题化和后续的重新样式化都只需更改一个文件。
规则:
- 两层:原始调色板(
--ink-900、--clay-500)和语义Token(--color-text、--color-accent、--color-surface)。组件只使用语义Token。 - 组件中没有魔法数字。每个颜色、大小、间隙、圆角、阴影、持续时间和z轴索引都来自一个Token。例外:
0、1px细线、100%和1fr。 - 深色模式:每个语义颜色都是
light-dark(LIGHT, DARK),这样两个主题都存在于一个声明中,组件永远不会改变。请参阅references/color-and-theming.md。
Tailwind:使Token成为主题,这样 bg-surface、text-text-muted、font-display 和 rounded-md 存在,而 bg-indigo-500 不存在。
- v4:
assets/tokens.css中的第二个块已经使用了Tailwind的命名空间(--color-*、--font-*、--text-*、--radius-*、--shadow-*、--ease-*)。将该块的:root更改为@theme并添加--color-*: initial;作为其第一行,以删除默认调色板。将第三个块(color-scheme)保留为纯CSS。实用程序每个主题解析light-dark(),因为它们引用var(--color-*)。不要写@theme inline { --color-x: var(--color-x) };一个引用自身名称的变量是一个循环,会解析为无。 - v3:在
tailwind.config中,将theme.colors(不是extend)设置为{ surface: "var(--color-surface)", ... },并以相同方式扩展fontFamily、borderRadius、boxShadow。
第4步 - 移动优先,包含每个状态
为360px视口编写基础样式,然后添加 min-width 媒体查询或容器查询。原因:添加空间比移除空间更容易,并且大多数布局错误(水平滚动、挤压的表格)只在较小的宽度下出现。
每个交互元素都附带其所有状态。缺失状态是UI感觉未完成的最常见原因。
| 状态 | 必须展示的内容 |
|---|---|
| 默认 | 静态设计 |
| 悬停 | 细微变化(颜色或阴影),仅在 @media (hover: hover) 下 |
| 焦点可见 | 清晰可见的环,永不移除 |
| 活跃 | 按下反馈(变暗,或 translateY(1px)) |
| 禁用 | 较低对比度 + cursor: not-allowed,以及真实的 disabled 属性 |
| 加载中 | 微调器或骨架屏,aria-busy="true",宽度不跳动 |
| 错误 | 字段附近的消息文本,不仅仅是红色边框 |
| 空状态 | 真实消息和下一步操作,永远不要是一个空盒子 |
首先使用语义化HTML:<button> 用于操作,<a href> 用于导航,<label for> 用于每个输入,<dialog> 用于模态框,<table> 用于表格数据,地标元素(<header> <nav> <main> <footer>)。仅当HTML没有原生等效项时才添加ARIA。原因:原生元素免费提供键盘行为和屏幕阅读器语义;ARIA是一个需要你实现其功能的承诺。
编写真实的文案。占位符文本(“Lorem ipsum”、“功能 1”、“张三”)会隐藏布局问题,并使结果看起来像模板。使用与第1步目的相符的具体、合理的内容。
第5步 - 交付前审查
运行 scripts/audit.md:bash scripts/audit.sh <changed files>(模式检查加上Token对比度检查),然后,如果Playwright可用或用户同意安装它,运行 node scripts/browser-check.mjs <page> 并查看其保存的屏幕截图。修复失败的内容。注释中写的比率是声明,不是测量值;请计算它们。不要在已知失败的情况下报告工作已完成;如果某些内容无法修复(例如,品牌颜色对比度失败),请明确说明。
多Agent模式(当有子Agent工具可用时)
主Agent总是自己执行第0-4步:思考和构建需要相同的上下文,拆分它们会丢失第0步发现的内容。仅在两个点上使用子Agent:
-
方向提案(第2步,仅限开放式简报)。 如果没有品牌、截图或现有设计系统,并行启动2-3个子Agent。给每个子Agent第1步的陈述,并为其分配表中 不同 的方向。告诉每个子Agent先阅读其对应的
references/directions.md和references/typography.md部分,并告知其他Agent的方向以便它们避免重复。每个子Agent仅返回:方向、字体配对、强调色+中性色色调、差异点、两个标志性动作。拒绝一个复用了另一个Agent的字体配对、强调色色调或差异点,或者保留了tokens.css起始值的提案。选择一个(或向用户展示选项),然后独自继续。 原因:一个Agent会收敛于平均值;强制不同的起点则不会。当品牌已经设定了方向时,跳过此步骤。 -
独立审查(第5步,始终适用于新页面或屏幕)。 在你自己的审查通过后,启动一个未编写代码的全新子Agent。给它更改后的文件、第1步的陈述和所选方向,并要求它:阅读
references/anti-patterns.md,运行scripts/audit.sh,并列出UI看起来通用、缺少状态或违反方向的每个地方。它将对每个发现进行排名:阻塞性/主要/次要,并且不进行编辑。修复所有阻塞性和主要问题,重新运行scripts/audit.sh,并在回复中列出未修复的次要问题。进行一次审查,不循环。 原因:构建者根据其意图创建的结果来判断,而不是根据屏幕上的实际内容。
无子Agent工具:自己按顺序进行两次。对于审查,重新阅读文件,就好像是别人写的一样。
质量规则(及其原因)
排版
- 不要默认使用 Inter、Roboto、Arial、Helvetica 或 system-ui 作为展示文本。也要避免较新的AI默认字体:Poppins、Montserrat、Space Grotesk、DM Sans。原因:这些是每个生成的页面都使用的,因此它们标志着“生成”。
- 将展示字体与在结构上形成对比的正文字体配对(衬线 + 无衬线、压缩 + 宽体、无衬线体 + 等宽体)。最多两个字体家族,加上一个可选的等宽字体。
- 使用带有流体大小(
clamp())的模块化比例,并使最大的标题明显大于正文字体。胆怯的层次结构(32px 对 16px)看起来未完成。 - 正文:最小16px,行高 1.5-1.7,行长 60-75个字符。密集的仪表板可以对元数据和表格单元格使用 12-14px,但永远不要用于段落或输入框。
颜色
- 一种主导颜色承载品牌;一种锐利的强调色保留给主要操作和关键数据。中性色完成大部分工作。大致 60/30/10。原因:均匀分布的五色调色板没有焦点,因此没有什么是重要的。
- 将中性色向品牌色调倾斜,而不是使用纯灰色。
- 除非品牌真的是紫色,否则不要使用紫色到蓝色或紫色在白色的渐变。
- 文本对比度至少 4.5:1(对于 24px+ 或 18.66px+ 的粗体文本,以及UI组件边界和焦点环,可为 3:1)。
布局和形状
- 避免相同的三或四列卡片网格。变化大小和重点:一个跨越两列的特色项目,一个列表旁边有一个大视觉元素,一个不对称的分割。
- 圆角遵循方向,并且在每一层保持一致。不要在所有东西上都使用
rounded-2xl;小的控件获得小的圆角,或没有圆角。 - 在320px宽度下没有水平滚动。在flex/grid子项上使用
min-width: 0,对长字符串使用overflow-wrap: anywhere,对宽表格使用滚动容器。 - 触摸目标至少 44x44px(内边距算)。
图标和图像
- 不要使用表情符号作为图标。使用一种图标集(Lucide、Phosphor、Heroicons、Tabler),具有相同的笔画宽度,或使用内联 SVG。原因:表情符号在不同操作系统上呈现不同,不能使用Token着色,并且会被屏幕阅读器朗读(“火箭”)。
- 装饰性图标获取
aria-hidden="true";仅包含图标的按钮获取aria-label。
动效
- 仅动画化
transform和opacity(外加偶尔的filter)。原因:其他属性会在每一帧触发布局或绘制,并在低端手机上出现卡顿。 - 动效持续时间来自Token:120-200ms用于UI反馈,250-400ms用于进入元素。
- 尊重
prefers-reduced-motion:移除移动,使淡入保持短暂或瞬间。
无障碍
...