关于 product-ui-taste
product-ui-taste 是面向密集型产品界面的设计技能,适用于构建仪表板、数据表、表单和管理控制台。它通过先分析数据、用户和操作后果,再选择布局与设计系统,避免原型式界面难以应对真实数据。
主要功能
- 01识别界面类型与数据规模
- 02按密度、复杂度和后果设定设计参数
- 03优先沿用现有设计系统
- 04为表格与多步骤流程选择合适结构
- 05处理空状态、加载和权限状态
适用场景
- 01设计可筛选和选择的密集数据表
- 02构建后台管理控制台
- 03规划包含高风险操作的设置界面
- 04制作多步骤向导
安装
复制下面的命令,在终端中运行。
npx skills add https:/ / github.com/ huytieu/ cog-second-brain --skill product-ui-taste安装后在 Claude Code 中输入/product-ui-taste确认已加载。
包含的文件
除 SKILL.md 外,安装时一起下载的参考资料和脚本。
references/block-skeletons.mdcanonical-sources.mdhouse-ui-rules.mdinstall-commands.mdsemantic-zoom.md详细介绍
product-ui-taste:适用于密集型产品界面的反套路 Skill
仪表板、数据表、表单、向导、设置、列表/详情页、管理控制台、应用框架。不适用于落地页、作品集或营销内容(这些适用
taste-skill)。不适用于图表(这些适用dataviz)。不适用于原生移动应用。 每条规则都取决于具体情境,且可机械执行。规则是否生效取决于你正在构建的界面,而不是自动触发。先了解界面、规划好布局,再只选用适合的规则。 配套约定:taste-skill明确将“仪表板 / 密集型产品界面 / 数据表 / 多步骤表单”交给“合适的工具”。这个 Skill 就是那个工具。如果需求一半是营销、一半是产品(例如嵌入实时仪表板的落地页),则营销主视觉和营销板块使用taste-skill,产品界面使用本 Skill。切勿在同一组件上同时使用两者。
0. 产品界面判断(先了解界面,再做其他事)
营销界面取胜于第一印象。产品界面则要经受真实数据的考验,满足用户第一百次使用时的需求。反套路设计的失误模式也不同:问题不在于“审美模板化”,而在于原型一接触真实数据就崩溃——表格导致整个页面横向滚动、按钮把自己的标签文字截断、一个灰色“无数据”框被重复用于三种不同情况,或表单在用户误触一个字段后清空所有内容。
0.A 首先判断这些信号
- 界面类型——最重要的判断。类型包括:索引/表格(用于扫描、筛选和选择记录的列表)、详情/记录(单个实体及其字段和相关数据)、仪表板/概览(关键指标 + 小组件 + 一两个表格)、表单/设置(输入和配置)、多步骤流程/向导(顺序执行的任务)、控制台/可观测性(日志、指标、部署)、信息流/消息(连续排列的条目)。多数实际界面都由两三种类型组合而成(如索引 + 详情抽屉;仪表板 + 下钻表格)。
- 数据结构和规模——第 95 百分位下有多少行、每行有多宽、单元格内容是否规整、数据是实时/流式还是静态,以及字段是否可能为空、为 null 或特别长。
- 所需密度——交易员的操作台和消费者的设置页面都属于“产品界面”,但两者所需的密度恰恰相反。判断用户属于专业日常使用者(紧凑、以键盘操作为主),还是偶尔使用的普通消费者(舒适、容错性强)。
- 操作后果等级——主要操作是信息性的(查看)、可撤销的(重命名、移动),还是破坏性/不可撤销的(删除、部署、扣款)。这决定了是否需要确认、默认焦点位置,以及是否适合自动保存。
- 用户身份和权限——用户角色、只读查看者、套餐/许可证等级。产品界面会出现营销界面没有的状态:无权限、只读、套餐限制(第 6.G 节)。现在就要考虑这些状态,而不是等到构建完正常流程后再处理。
- 现有系统——应用是否已经使用 Carbon、Fluent、Polaris、Atlaskit,或自有设计令牌集?产品界面几乎从来不是从零开始。引入设计风格前,先与现有系统保持一致。
0.B 生成前先输出一行“产品界面判断”
编写任何代码前,先说明:“判断为:面向 <user> 的 <surface type>,密度为 <density>,数据量为 <data volume>,操作后果为 <level>,基于 <design system | house tokens | none> 构建。”
示例:
- “判断为:面向内部运维人员的索引 + 详情抽屉,密度紧凑,约 2k 行(使用虚拟化),操作后果可撤销,基于自有系统构建(Table + 侧边栏检查器)。”
- “判断为:面向终端客户的设置/表单界面,密度舒适,数据量较少,操作后果各异(账单操作具有破坏性),基于自有系统的表单布局构建。”
0.C 最重要的反默认原则:用行,而不是卡片
让产品界面看起来像 AI 原型,最快的办法就是把每条记录都包进带徽章的卡片里。用户需要扫描、筛选、排序或选择的密集数据,应以行的形式呈现(按列排列时用 Table,单行时用 List/Item),铺满整个宽度,并用分隔线分隔,行高为 32-40px。卡片是组件容器(KPI 磁贴、图表面板、设置分组、画廊条目),绝不能作为列表项的外层容器。[Material3 lists; Carbon] 如果你看到记录列表时,第一反应是用卡片网格,请停下来重读这一条。
0.D 如果需求不明确,只问一个问题,然后继续
值得提出的问题几乎总是关于数据量或后果(这会改变架构):“数据量最大时大约有多少行?”或“这个操作可以撤销吗?”其他信息都从产品解读中推断,并说明你的假设。不要进行访谈。
0.E 反默认原则
模型在产品 UI 中的默认做法包括:卡片堆砌、固定宽度且溢出页面的 <table>、把 disabled 提交按钮作为唯一验证方式、只有一种通用空状态、z-index: 9999,以及每次加载都显示全页加载指示器。每种做法都有下文中对应的正确替代方案。发现自己正要采用其中一种时,就该打开相应章节。
1. 三个调节项(核心配置)
完成产品解读后,设置三个调节项。与用于表现力营销设计的品味技能(调整 VARIANCE / MOTION / DENSITY)不同,产品调节项针对信息处理进行调整。产品 UI 中几乎不使用动效;动效用于反馈,而非装饰。
DENSITY: 2- 1 = 舒适/面向消费者,2 = 标准,3 = 紧凑/控制台风格。决定行高、内边距比例、字体大小,以及是否提供密度切换器。DATA_COMPLEXITY: 2- 1 = 简单列表(普通 Table/List),2 = 可排序、可筛选、可选择的表格,3 = 企业级网格(冻结列、虚拟滚动、列管理、实时数据)。CONSEQUENCE: 2- 1 = 信息展示/只读,2 = 可撤销的变更,3 = 破坏性/不可逆/财务操作。决定默认确认机制、自动保存的适用性、错误提示语气,以及“未保存的更改”提示的醒目程度。
基准值:2 / 2 / 2(带详情视图的标准 SaaS 产品表格)。根据产品解读进行调整。
1.A 调节项推断
| 信号 | DENSITY | DATA_COMPLEXITY | CONSEQUENCE |
|---|---|---|---|
| “内部管理 / 运维工具 / 专家日常使用” | 3 | 2-3 | 2 |
| “企业数据控制台 / 网格 / 分析” | 3 | 3 | 2 |
| “消费者设置 / 账户 / 引导流程” | 1 | 1 | 1-2 |
| “SaaS 产品仪表板(默认)” | 2 | 2 | 2 |
| “计费 / 删除 / 部署 / 权限界面” | 匹配 | 匹配 | 3 |
| “可观测性 / 日志 / 实时指标” | 3 | 3 | 1 |
1.B 用例预设
| 预设 | DENSITY | DATA_COMPLEXITY | CONSEQUENCE | 框架 |
|---|---|---|---|---|
| 内部管理工具 | 3 | 2 | 2 | AppShell + SideNav,选择后显示检查器 |
| SaaS 产品仪表板 | 2 | 2 | 2 | AppShell + SideNav 或 TopNav + TabList |
| 企业数据控制台 | 3 | 3 | 2 | AppShell + SideNav,全高网格 |
| 消费者设置/账户 | 1 | 1 | 1-2 | AppShell + 设置模板(导航 + 表单面板) |
| 多步向导 | 1 | 1 | 2-3 | 居中列、步骤指示器,不添加干扰性界面元素 |
1.C 调节项如何影响输出
在推理中将这些准确名称用作全局变量。DENSITY 决定行高和是否需要密度切换器(第 10.A 节)。DATA_COMPLEXITY 决定表格深入分析(第 7 节)的深度。CONSEQUENCE 决定第 6.G、8.E、8.F、9.A 节的内容。不要自创别名。
2. 产品类型 → 设计系统映射
产品 UI 几乎不会完全手写 CSS。首先根据宿主应用选择基础系统,其次再考虑产品类型。**每个应用只用一套系统。**不要混用 Carbon 和 Fluent,也不要把 shadcn 放进 Polaris 组件树中。
2.A 优先使用设计系统(先了解,不要猜测)
如果应用已有设计系统,应以该系统为准。你的首要任务是查清它实际提供了什么,而不是想当然。大多数成熟系统已经解决了本技能关注的密集界面问题,因此你很少需要退回到原始 CSS。猜测组件名称和属性,是 Agent 写出看似正确却无法编译的代码最常见的原因。
编写 UI 之前,请通过宿主系统的真实 API(CLI、文档网站或 .d.ts 文件)逐一确认以下内容:
| 需求 | 查找内容 | 确认方式 |
|---|---|---|
| 全页面外壳 | 应用外壳基础组件及其导航插槽 | 查阅系统文档中的“layout”或“app shell”页面 |
| 多窗格工具 / 检查器 | 布局与侧面板配对,而不是两个浮动的 div | 查阅“layout”文档页面;确认面板是否可调整大小 |
| 列式数据 | 系统的 Table 及其排序 / 选择 / 分页插件 | 查阅组件参考,而非博客示例 |
| 粘滞 / 冻结列 | 专用 hook 或插件(大多数系统都提供) | 手工实现前,先在文档中搜索“sticky”或“frozen” |
| 可调整大小的列 | 列宽调整 hook | 同上 |
| 分页页脚 | Table 分页与每页条数控件 | 查阅组件参考 |
| 搜索 + 筛选工具栏 | 有别于普通搜索的工具栏 / 筛选组件 | 查阅组件参考 |
| 单行记录 | List / Item 配对 | 查阅组件参考 |
| 空白区域 | EmptyState 组件 | 查阅组件参考 |
| 状态 / 元数据 | 有别于 Badge 的状态指示器 | 查阅令牌 / 状态文档 |
**工作流,始终如此:**先找到系统已有的最相近页面或区块模板,研究其布局骨架,然后查阅你要使用的每个组件的真实属性。**绝不编造属性。**优先使用系统自带的布局组件,而非原始 <div>,以确保间距符合系统的尺度。所有数值都使用设计令牌(var(--color-*)、间距令牌),绝不使用原始十六进制颜色值或 px。Badge 用于计数和枚举状态;状态应使用状态指示器。
设计系统衔接方式(化解矛盾):第 5-10 节中的 CSS 机制(滚动归属、position:sticky 表头、冻结列偏移、z-index 层级、min-width:0)是成熟系统的 hook 在底层已满足的约定。在这类系统中,应优先使用 hook 或插件,因为真正的粘滞列 hook 能正确处理偏移、纯色背景和阴影分隔线。只有在以下情况下才退回原始 CSS 约定:(a) 某些内容透出时,用于验证系统是否正确实现;或 (b) 完全不在设计系统中。若系统 Table 提供了 hook,绝不要在其中用内联样式手工实现粘滞列。
2.B 外部系统(匹配宿主,或按类型选择)
| 类型 / 简述 | 选用 | 原因 |
|---|---|---|
| IBM 风格企业分析、密集网格 | @carbon/react + @carbon/styles | 数据密度、2x 网格以及空状态 / 加载模式最成熟 [carbon] |
| Microsoft / 企业 SaaS、DataGrid | @fluentui/react-components (v9) | 官方 Fluent 2、Overflow 基元、令牌 [fluent] |
| Atlassian / Jira 风格产品 | @atlaskit/* + @atlaskit/tokens | 按含义选择语义化间距令牌 [atlaskit] |
| 商家 / 管理后台、资源列表 | Polaris(@shopify/polaris 或 Web 组件) | IndexTable、四态理念、错误内容 [polaris] |
| GitHub 风格开发工具界面 | @primer/react / @primer/css | PageLayout 地标、Blankslate、Truncate [primer] |
| 自适应 / 规范布局、偏消费级 | @material/web + Material 3 令牌 | 窗口尺寸类别、列表-详情 / 辅助面板 [material3] |
| 代码自有、精简型内部工具 | Radix Primitives + shadcn/ui + TanStack Table | 无头无障碍基元,样式由你掌控 [radix, tanstack] |
| 追求快速构建的企业级 React 密集数据应用 | Ant Design 或 Mantine + Mantine React Table | 开箱即用的 Table / Form [ant, mantine] |
| 精简、键盘优先(Linear/Vercel 风格) | Radix/shadcn + 自定义令牌 | 密度 + 命令面板 + 克制 [linear, vercel] |
**诚实原则:**如果应用属于上述类型,请使用官方包及其 Table/Form/Dialog。不要用原生 CSS 重建 Carbon 数据表格。不要导入某个系统的令牌后再覆盖其中 90%。
3. 先构建框架的应用外壳架构
**在编写任何内容前,先按像素规划框架。**真实应用自上而下构建:选择外壳,命名各个区域,为每个区域分配明确的像素预算,确定容器策略和响应式行为,然后再填充内容。内容优先的布局(先编写各个区块,再逐一套上 Card)会形成带内边距的滚动列,看起来像原型。[most systems' layout docs]
3.A 选择外壳
- AppShell(顶部栏和 / 或侧边导航)- 默认的全页产品框架。
- Layout + LayoutContent + LayoutPanel - 多面板工具:浏览器、控制台、带检查器的主从视图。
- 普通内容列 - 单个表单、设置区块、向导。
3.B 区域像素预算(请记住)
| 区域 | 预算 |
|---|---|
| 侧边导航 | 240-280px |
| 图标栏(折叠的导航) | 64-72px |
| 详情 / 检查器面板 | 340-420px(可调整大小,320-480) |
| 筛选器 / 分类导航栏 | 220-260px |
| 行高 | 紧凑型 32px / 标准型 40px / 舒适型 48px [密集型系统预算为 32-40;Carbon 提供 5 个相对等级,不设固定像素值] |
| 顶栏 / 顶部导航 | 48-64px(常见应用栏惯例) |
3.C 地标区域(不要混淆)
为每个区域指定真实地标,并了解它们之间的区别 [primer PageLayout]:
- Header - 全宽、位于顶部,是应用界面框架。
- Content - 主要区域,弹性扩展以填满空间。
- Pane - Content 旁边的次要区域,高度仅与内容相同。用于检查器或辅助信息。
- Sidebar - 全高导航,与 Pane 不同。不要把高度仅与内容相同的面板称作“侧边栏”,也不要把全高导航称作“面板”。
- Footer - 全宽、位于底部(状态栏、分页上下文)。
3.D 规范布局(识别并搭建)[material3]
- 列表-详情 - 左侧为列表,右侧为选中的记录。在约 1024px 以下,详情会以覆盖层显示或变为单独的路由。
- 辅助面板 - 主要内容约占 2/3,辅助面板约占 1/3。
- Feed - 单个滚动流(行 / 气泡,不在流中使用卡片)。
- 带检查器的主从视图(工具的骨干布局)- 选中一行后打开固定宽度的 LayoutPanel 检查器,而不是导航到其他页面。添加
resizable。在约 1024px 以下,将其显示为覆盖层,而不是压缩 Content。
3.E 营销界面不会有的应用外壳元素
- 工作区 / 组织切换器 - 几乎每个 SaaS 外壳都需要一个,固定在侧边导航顶部或页眉左侧,显示当前租户的名称 / 头像。点击后打开下拉菜单(而不是模态框),列出工作区;达到 8 个以上时,提供搜索框,还有一个“最近的工作区”分组,以及底部的“创建 / 加入”操作。它用于切换上下文(数据范围),不是导航目的地。将其作为一个独立元素;切勿将租户选择分散到导航列表中。按用户保存上次活跃的工作区。
- 全局命令面板(Cmd/Ctrl-K)- 对于 DATA_COMPLEXITY >= 2 的键盘优先工具,这是最快的导航方式。应将其视为外壳的一级功能,而非可有可无的附加项。
- 后台任务 / 异步状态栏 - 当应用启动多个并行的长时间运行操作(导入、导出、部署)时,持久的通知栏(带数量徽标的页眉图标,点击后打开显示任务的下拉菜单)胜过每个任务都弹出一个模态框。每行任务都显示确定进度条、取消操作,以及终止状态的成功 / 错误信息和结果链接。Carbon 的规则:等待超过几分钟时,应允许用户离开,并在完成后通知。切勿因一个后台任务阻塞整个 UI。[carbon loading]
3.F 各区域的容器策略(按产品原型决定卡片还是行)
| 产品原型 | 容器策略 |
|---|---|
| 跟踪器 / 工作工具(问题、工单、CRM) | 仅使用行。分组的通栏列表,不使用卡片。 |
| 控制台 / 可观测性 | 仪表板组件使用卡片网格;其他所有内容使用表格。 |
| 消息 / 信息流 | 使用行和气泡。信息流中不使用卡片。 |
| 媒体 / 图库 | 使用卡片网格(ClickableCard)+ 详情中的紧凑元数据行。 |
| 设置 / 表单 | 使用 FormLayout 分区;仅使用卡片对危险操作 / 账单操作进行分组。 |
禁止:每个列表项都使用卡片(卡片泛滥)、将全宽卡片堆叠起来作为页面结构、嵌套卡片、将 Badge 用作装饰。
4. 布局硬性规则(违反任意一条都会导致成品出问题)
- 滚动归属(产品 UI 布局中排名第一的问题)。 每个滚动轴恰好只能由一个包装元素拥有(
overflow:auto)。页面<body>绝不能水平滚动。宽表格应在自己的容器内滚动,而不是把整个应用推向一侧。粘性页眉使用限定在该滚动包装元素内的position:sticky,绝不能使用相对于视口的position:fixed。[carbon, ant table-scroll, tanstack] 如果两个嵌套元素同时滚动同一个轴,就是一个 bug。 - 每个粘性元素都需要有界高度的祖先元素。 如果没有祖先元素建立滚动边界,
position:sticky会悄无声息地失效。粘性表格页眉要求表格的滚动容器具有实际的最大高度。[css mechanics] - 包含文本的 flex 子元素要设置
min-width:0。 flex/grid 子元素默认使用min-width:auto,这会导致其无法收缩到小于内容尺寸,因此text-overflow:ellipsis会悄无声息地失效,子元素反而会把布局撑得更宽。flex 行内任何需要截断的文本,都必须在承载文本的子元素上设置min-width:0。这是“我的省略号怎么不起作用”最常见的原因。[overflow-truncation] - 预先声明各区域的响应式规则。 在填充内容前,先写明各区域会在哪些断点收起、覆盖或隐藏。不要假设“Tailwind 会处理”。导航栏应收起为图标栏或画布外的 MobileNav;检查器应在约 1024px 以下以浮层显示;宽表格应保留水平滚动,而不是重新排列表格列。
- 断点:使用尺寸类别,而非设备名称。 可使用 Compact / Medium / Expanded(Material 3)或 Carbon 的精确 2x 网格断点(320 / 672 / 1056 / 1312 / 1584,8px 迷你单位、16px 内边距、32px 槽距)作为备用网格。[material3, carbon 2x-grid]
- 可复用组件使用容器查询。 同一张卡片或面板可能出现在宽阔的 Content 区域和狭窄的 Pane 中,应根据自身宽度响应,而不是视口宽度。对于会在多个区域宽度中渲染的内容,使用
container-type+@container,而不是视口媒体查询。[joshwcomeau, responsive-layout] - 侧边栏收起时应移出画布或变成图标栏,而不是原地变窄。 将 260px 的导航栏收窄到 180px 只会让内容变得拥挤。应收起为 64-72px 的图标栏(带工具提示),或将其滑出画布并保留一个常驻的重新打开条。调整窗口大小或折叠布局时,应保留各面板是否显示的状态,避免用户丢失上下文。
- 密度是一种布局模式,而不是 CSS 缩放。 至少应将 DENSITY 拨盘要求的密度预设作为实际切换选项提供(切换行高 + 内边距令牌),而不是使用浏览器缩放技巧。[i18n-a11y-density, carbon 5 row heights]
- 绝不要在文本容器上硬编码固定宽度。 徽章、按钮、表格标题、芯片、标签都必须采用自动布局(flex/grid、贴合内容),以便文本能够换行或扩展。固定宽度的文本容器在真实数据和翻译文本下都会发生裁切(第 10.B 节)。
- 用于比较的数字应使用等宽数字。 用户需要纵向比较的任何数字列或数字列表,都应使用
font-variant-numeric: tabular-nums,使数字对齐。指标列中出现比例数字就是个明显问题。
5. 溢出与截断目录(深入探讨)
产品 UI 中会出现真实字符串:90 个字符的文件路径、德语按钮标签、只有一个词或长达四十个词的客户名称。截断应当像手术刀一样精准,而不是默认做法。
5.A 绝不截断清单(必须遵守)
绝不截断主要内容或标识性内容:标题、实体名称、唯一 ID、错误和验证消息、页面标题、按钮标签。截断只适用于次要/补充内容(描述、次要元数据、面包屑中间部分)。[carbon overflow, primer truncate] 如果标识性字符串太长,应让它换行、为它提供更多空间,或使用保留首尾内容的中间截断(适用于路径/ID)——绝不要用尾部省略号隐藏区分两条记录的部分。
5.B 每个被截断的字符串都需要真正显示完整文本的备用方式
仅通过悬停显示 title 工具提示并不是无障碍的备用方式:键盘用户、触控用户和语音识别用户都无法使用。应在获得焦点时也显示完整文本,或提供可展开的行内内容,或在详情视图中显示完整文本。[carbon, primer]
5.C 按钮标签过长(必选,是产品 UI 水平的鲜明体现)
**绝不截断按钮标签。**让按钮宽度适应其内容(“hug contents”),并设置 min-width 下限,以确保触控目标的无障碍性。如果标签较长(或经过翻译),应先换行,最多显示 2 行,然后再考虑截断,并重新评估标签文案。按钮上显示“Save chang...”就说明实现有问题。纯图标按钮需要 aria-label 和工具提示。[overflow-truncation, i18n]
5.D 单行省略号方案
overflow:hidden; text-overflow:ellipsis; white-space:nowrap; 以及文本与滚动容器/行容器之间每个 flex 祖先上的 min-width:0(第 4 节)。如果没有这条 min-width:0 链,CSS 就不会生效。
5.E 多行截断方案(-webkit-line-clamp)
四个属性必须同时使用,否则都不起作用:display:-webkit-box; -webkit-line-clamp:N; -webkit-box-orient:vertical; overflow:hidden;。不要在被截断的元素本身设置内边距——截断计算会忽略内边距,导致文本溢出。如果需要内边距,请将截断元素包在带内边距的父元素中,或使用固定高度作为备用方案(line-height * N)。[css-tricks line-clampin]
5.F 内容边界情况:计数、大数字、缺失数据(必选)
真实数据可能是 0、1 或 1,000,000——这三种情况都要设计,不能只考虑演示数据中的“3”。
- **零/一/多个(复数形式):**每个计数字符串都要正确处理 0、1 和 N——“0 个项目”“1 个项目”“24 个项目”,绝不能出现“1 个项目们”或“24 个项目”。使用支持 ICU/复数形式的格式化程序,绝不要拼接字符串;有些语言最多有 6 种复数形式。零通常应显示空状态(第 6.C 节),而不是在完整布局中显示“0 个项目”。
- **非常大/非常精确的数字:**在密集内容中缩写数字以便快速浏览(1.2k、3.4M、1.1B),但要确保用户可以通过悬停/聚焦和详情视图获取精确值;绝不要缩写用户必须精确操作的数字(例如发票总额、账户余额)。使用符合区域设置的数字分组格式(1,234.56 与 1.234,56)、对比列使用等宽数字(第 4 节),并为何时开始缩写设定一致的阈值。
- **缺失/null/不适用(通用规则,不只适用于表格单元格):**null 字段应统一显示低调的占位符(“未设置”“-”“未知”),并在视觉上区别于空字符串和零。针对每个字段,判断 null 表示“尚未设置”(提示输入)、“不适用”(简要说明),还是“因权限而隐藏”(第 6.G 节)——这三种情况应使用不同的占位符,绝不能留白,让人误以为是渲染错误。
- 没有空格的长标记(URL、哈希、文件路径、ID):使用保留首尾内容的中间截断,或使用
overflow-wrap:anywhere;绝不要仅在末尾添加省略号,以免隐藏可用于区分的末尾内容,也绝不能让标记撑破列宽。
5.G 列标题和界面装饰元素溢出
- 列标题:最多换行 2 行,然后截断,并通过工具提示显示完整标题。[carbon]
- 面包屑:将中间部分折叠为省略号/溢出菜单,保留首项和末项。[carbon overflow]
- 工具栏/操作栏/标签页:使用优先级溢出(Fluent Overflow):声明可见优先级顺序和
minimumVisible下限,将其余项移入“更多”菜单,并按反向 DOM 顺序渲染,以确保第一项优先显示。[fluent] - 较长的自由文本内容块:可展开的正文使用“展开更多/收起”,列表使用“加载更多”(第 7.D 节)。
6. 状态矩阵(深入分析)
每个数据区域都不只有理想情况。请从三个粒度设计完整矩阵:页面、组件/区域和字段。敷衍的做法是用同一个灰色框应付所有非理想状态。
6.A 四种基础状态,始终一起设计
对于任何会加载数据的区域,在发布理想状态前,先设计理想状态 / 加载状态 / 空状态 / 错误状态。[polaris four-states, nngroup]
6.B 加载时间阈值(强制要求)
- < 1s:不显示任何内容。一闪而过的骨架屏比短暂静止更糟。
- 1-10s,整页 / 区域加载:使用与实际布局形状相符的骨架屏(即将呈现的实际行 / 字段),绝不能只用灰色框,也绝不能为结构化内容使用加载指示器。[nngroup skeleton, carbon loading]
- > 10s,或时长未知的工作(上传、下载、转换、部署):使用确定进度的进度条,绝不能使用不确定进度的加载指示器或骨架屏。对于需要数分钟的工作,允许用户离开,并在完成时通知用户。[carbon]
- 已显示数据的后台刷新:保留旧内容可见,并显示不显眼的行内指示器。刷新时绝不能将缓存内容重新清空,改为显示加载指示器 / 骨架屏。
6.C 空状态分为三种不同状态(强制要求)
绝不能在以下状态中重复使用同一个空状态组件 / 消息:
- 尚未使用(首次运行)——采用引导语气:说明这是什么,并提供用于创建第一条记录的主要 CTA。[primer empty-states, pencilandpaper]
- 筛选 / 搜索无结果——显示“没有关于 X 的结果”,并提供清除筛选条件的操作。数据确实存在;原因是筛选条件。
- 零结果是好事——例如“收件箱已清空”“没有提醒”。语气积极,不要用 CTA 让用户感到内疚。 [carbon empty-states, atlaskit empty-state] 对于很小的卡片 / 侧边面板,居中显示纯文本空状态是“与布局相符”原则的例外。
6.D 错误状态绝不是空状态
加载失败始终属于 ErrorState,绝不能是 EmptyState。错误文案应具体、通俗易懂、可操作(说明下一步该做什么),并附带供支持人员查询的错误代码 / ID。统一通过一个共享的 ErrorState 组件处理所有错误,以保持语气和布局一致;绝不能只显示一句“发生错误”。[polaris error-messages, nngroup]
6.E 字段和行内状态
字段有自己的微观状态:异步验证待处理(第 8.D 节)、行内错误、成功确认、保存中 / 已保存(自动保存,第 8.E 节)。字段级失败不能悄无声息地无响应。
6.F 部分加载、数据过时和离线状态(必须考虑)
在“已加载”和“失败”之间,还有真实网络环境带来的状态:
- **部分加载 / 部分失败:**当组合视图加载时,如果某个小组件失败,只在该小组件原处显示错误(显示自己的行内 ErrorState 和重试操作),绝不能因为一个面板失败就清空整个页面。一个损坏的卡片导致仪表盘中所有卡片都无法使用,这是一个 bug。
- **数据过时 / 已缓存 / 刷新中:**显示最后一次正常获取的数据,并提供不显眼的“正在更新”指示器和“上次更新于 ”时间戳;后台刷新时绝不能重新显示骨架屏(第 6.B 节)。刷新失败时,保留过时数据,并显示不阻塞操作的提示:“无法刷新,正在显示 的数据”。
- 离线 / 断开连接:检测连接丢失,显示持续存在的非模态横幅,禁用或排队变更,并在重新连接后进行协调。绝不能让用户在无法静默保存的表单中输入内容。
- **乐观更新约定:**如果在服务器确认前就渲染变更,必须提供回滚路径——恢复先前的值,并在写入失败时显示清晰的错误。没有回滚机制的乐观更新是数据完整性隐患。
6.G 权限与计划状态(仅限产品 UI,必须考虑)
营销 UI 没有这些概念;产品 UI 则离不开它们:
- 权限不足/角色限制:有意决定采用禁用还是隐藏。隐藏用户永远无法使用的操作;对于用户更改角色后可以使用的操作,则应禁用(悬停/聚焦时说明原因)。切勿显示没有任何说明、无法操作的控件。
- 只读模式:保持相同布局,将输入项呈现为静态值,并明确标示“只读”;不要把输入项显示成灰色,让人误以为它们已损坏。
- 计划/许可证锁定:锁定功能的提示(“升级以解锁”)是一种独立状态,应有专属组件——明确显示并说明原因,且提供升级路径——而不是一个禁用按钮。[保持与宿主应用已有的惯例一致。]
7. 数据表深入解析(最复杂的产品界面)
根据 DATA_COMPLEXITY 控制深入程度。简单列表需要 7.A-7.B;企业级网格则需要全部内容。在设计系统中,应使用其 Table 以及系统的插件/钩子;以下机制是这些钩子必须满足的约定,也是你在系统之外实现时的指南。
7.A 滚动归属与固定表头(必须)
使用一个同时负责两个方向的容器(overflow:auto),并设置高度限制;页面本身不得横向滚动(见第 4 节)。通过 position:sticky; top:0 固定表头,并将其作用范围限定在该容器内。[carbon, ant table-scroll]
7.B 冻结/固定列(必须采用的方案)
使用 position:sticky + 明确的 left:0(或 right:0)偏移量 + z-index >= 10(高于滚动内容)+ 不透明的纯色背景,且背景也要覆盖悬停和选中行状态 + 内嵌 box-shadow 分隔线,绝不要使用 CSS border(在粘性滚动时,边框会消失或重复渲染)。如果缺少纯色背景,滚动内容就会透过冻结列显现。[ant, mantine-react-table column-pinning, tanstack]
7.C 虚拟化(DATA_COMPLEXITY 3)
超过约 100-200 行时进行虚拟化。所需的 DOM 结构:两个容器——外层为可滚动的视口高度容器,内层为完整的虚拟内容高度(占位空间)。绝不要将 <table> 元素本身设置为完整的虚拟高度——这会扭曲行高并导致粘性表头折叠(表头无法脱离过矮的父级)。[tanstack virtual #640] 虚拟渲染时,不要附加逐行样式对象或逐行菜单实例(会严重拖慢性能);除非经过记忆化处理,否则使用 columnResizeMode:"onEnd"。[ant, mantine]
7.D 分页、无限滚动与“加载更多”(决策树)
- 分页:索引超过约 50 项后就应分页(Polaris 的明确阈值);对于“查找特定项目/比较相距较远的项目/查看前几项”这类任务,始终采用分页。[polaris, smashingmagazine]
- 无限滚动:只适用于探索型、同质且无尽的内容流——绝不能用于搜索结果或比较任务。如果使用,应实现
history.pushState(),确保返回按钮能恢复滚动位置(超过 90% 的实现都处理不当)。在移动端,一次加载一个较大的平铺批次(15-30 项);绝不要在滚动过程中分批延迟加载(主动滚动时获取数据并不可靠)。[nngroup infinite-scrolling, baymard] - “加载更多”按钮:对于数量有限但较长的列表,这是稳妥的折中方案;由用户控制数据获取。
7.E 选择、批量操作与选择状态持久化
- 行选择应支持 Shift 单击选择范围以及全选。文案要区分“选择全部 N 项(跨页)”和“选择此页的全部项目”。[polaris]
- **选择状态持久化(企业产品中常见的正确性缺陷):**在分页、排序和筛选条件变更后,保留所选行的 ID;翻页时不得悄悄清除选择。显示持续更新的“已选择 N 项”计数,并在页面导航后保留。
- 批量操作进行期间,禁用各行操作,以防止产生冲突的变更。
- 工具栏操作上限:最多显示 5 个主要操作,其余操作放入溢出菜单。[polaris]
7.F 筛选、分面与搜索(彼此不同)
- 搜索(自由文本)和筛选(结构化谓词)是不同的控件;不要悄悄将它们合并到同一个输入框中。筛选控件应与其数据类型匹配(日期范围、枚举多选、数值范围)。[data-table-mechanics;大多数系统都提供工具栏筛选组件]
- 已应用筛选条件标签:在表格上方用一行可移除的标签显示已启用的筛选条件,并提供“全部清除”选项;对于有很多分面的情况,还应提供分面侧边栏。这是 Jira/Linear/Notion 当前的标准模式;如果没有标签行,用户就无法查看或撤销正在筛选数据的条件。
7.G 单元格内容
单元格中可能包含不规则的真实数据。按第 5 节的规定截断次要单元格(完整文本须可通过焦点访问),但绝不要截断用于识别的单元格。数值列右对齐,并使用 tabular-nums(第 4 节)。空单元格应使用一致的“未设置”占位符(禁止使用 em-dash;请使用“-”或“未设置”),绝不能留空,以免看起来像渲染故障。
8. 表单、验证与多步骤流程(深入探讨)
8.A 标签位置
默认将标签置于顶部(扫描速度最快,也最适合本地化和移动设备)。仅在垂直空间有限的密集型企业表单中使用右对齐标签。只有在有意让用户放慢速度、仔细完成任务时,才使用左对齐标签。对于本地化产品,绝不要将标签右对齐(文本扩展会破坏列布局)。[lukew web-form,nngroup web-form-design]
8.B 验证时机
在失焦和提交时验证,不要在每次按键时都验证未完成的值(不要在用户刚输入第一个字符时就提示“电子邮件无效”)。例外情况是密码强度这类实时正向提示。提交时,既要在字段旁显示行内错误,也要在顶部显示汇总错误摘要,不能只显示其中一种。[nngroup errors-forms,polaris inline-error]
8.C 绝不能只通过禁用“提交”按钮来提示问题(必需)
对于包含 5 个或更多字段的表单,绝不要将禁用的“提交”按钮作为唯一反馈——用户无法判断缺少什么。允许用户提交,然后显示错误总数,并将焦点移至第一个错误处。只有在简单(少于 3 个字段)的表单中,才可以禁用“提交”按钮。提交失败后始终保留用户输入——绝不要清空表单。[forms-flows,nngroup]
8.D 异步/远程字段验证
对请求进行防抖处理,并且使用 AbortSignal 取消已被替代的请求——未取消的慢速响应可能会覆盖更新且正确的结果(用户名可用性竞态)。[forms-flows]
8.E 自动保存(按后果等级把关)
- 按逻辑字段组自动保存,绝不要在每次按键时保存整个表单。
- 绝不要自动保存密码、可见性/权限、机密性或会产生财务影响的字段——这些字段必须由用户明确手动确认(后果等级 3)。
- 在失焦时,以及最后一次按键后约 3 秒触发保存。显示“正在保存…”,然后显示“已保存 <相对时间>”。
- 自动保存失败时,应显示持续保留(不会自动消失)且带重试选项的 toast,绝不能静默失败。
8.F 多步骤流程/向导
- 步骤指示器必须对应实际步骤;已完成的步骤应可点击返回。不要使用虚假的“阶段 1/阶段 2”标签——应写明实际步骤名称。[taste-skill tell parity]
- 要减少向导中途放弃的情况,应减少字段数量,而不是增加步骤。不要在流程中途插入追加销售内容。[baymard checkout-linear]
- 保持流程线性;分支式向导需要提供清晰可见的流程图。
8.G 破坏性/不可逆操作确认(后果等级 3,必需)
删除/部署/扣费的确认对话框应默认将键盘焦点置于安全操作(取消),而不是破坏性操作。使用 role="alertdialog"、aria-modal="true",并设置 aria-describedby 指向警告文本。对于后果严重的删除操作,必须要求用户输入资源名称,才能启用破坏性按钮。关闭对话框时,将焦点返回到打开对话框的元素(或下一个逻辑元素)。[w3 alertdialog,radix dialog]
9. 导航、叠加层与层叠(深入探讨)
9.A 对话框 / 模态框
打开时捕获焦点,关闭时将焦点还原;按 Escape 关闭;只有在非破坏性操作中,点击遮罩层才会关闭。即使视觉上隐藏,也绝不能省略 Dialog.Title 和 Dialog.Description(屏幕阅读器会读出它们)。[radix dialog, w3 dialog-modal]
9.B Toast / 通知
默认时长约为 5000ms,悬停或获得焦点时暂停,滑动关闭阈值约为 50px,并支持手动关闭。区分前台播报(用户自己的操作已成功)和后台播报(发生了某些变化),以确定屏幕阅读器的播报优先级。绝不要在 toast 中放置破坏性操作的确认流程。[radix toast]
9.C 菜单、下拉菜单、标签页、组合框
完整支持键盘操作:方向键导航、Home/End、按键快速查找、roving tabindex(第 10.D 节)。Tab 在复合控件与下一个内容之间移动;方向键在复合控件内移动。应保持键盘可访问的菜单项 / 标签项要使用 aria-disabled,而不是原生 disabled(后者会将它们从 Tab 键顺序中移除)。[radix, w3 grid]
9.D z-index / 堆叠上下文系统(强制要求,取代临时设置 z-index)
- 为每个层级分配数值间隔较大的数值,并一次性定义为 token,例如 base 0、sticky 100、dropdown 1000、sticky-header 1050、overlay/drawer 1100、modal 1200、popover 1300、toast 1400、tooltip 1500。绝不要孤立地手动调整单个 z-index;调整时要以整个数值体系为依据。临时设置
9999/99999是个危险信号。 - 调试层叠问题前,先列出所有会触发堆叠上下文的情况:带 z-index 的
position:fixed/sticky、opacity < 1、值中包含上下文属性名的transform/filter/will-change、contain、container-type、isolation:isolate、顶层元素。[mdn stacking-context] - 嵌套堆叠上下文是原子性的:深层嵌套元素上的极大 z-index,无法超越浅层祖先的同级元素。如果菜单被页头遮挡,应该修复祖先上下文,而不是增大数值。
- 覆盖层按打开顺序叠放,而不是使用按类型静态分配的常量:最后打开的覆盖层位于最上方。可用时,使用层叠管理器 / 平台顶层(
<dialog>、popover API)。
10. 密度、国际化与无障碍(深入探讨)
10.A 将密度作为一等模式
提供 >= 3 种密度预设(舒适 / 标准 / 紧凑),作为真正的切换模式,用于替换行高和内边距 token,而不是缩小缩放比例。[i18n-a11y-density] Carbon 提供 5 种行高;根据 DENSITY 调节项的需求选择一组。
10.B 国际化文本扩展余量(强制要求)
设计容器时要预留翻译空间,因为英语是最精简的 UI 语言之一:
- 德语:通常增加 30-40%,最坏情况下可达约 70%。
- 法语 / 俄语 / 西班牙语:增加 20-25%。
- 芬兰语:最多增加 100%(长度翻倍)。
- CJK:文本会收缩,而不是扩展。 绝不要将文本容器的宽度硬编码为固定值(第 4 节)。使用 flex/grid,让文本重新排布,而不是被裁切。伪本地化(在字符串前后加上带重音符号的填充字符)是一种快速冒烟测试,可发现约 80% 的文本扩展问题。[crowdin, i18n-a11y-density]
10.C RTL
只镜像有方向性的图标(后退 / 前进箭头、尖括号、进度方向)。绝不要镜像通用图标(时钟、播放、勾号、徽标)。即使位于 RTL 文本中,数字仍保持 LTR 排列。主操作按钮移至左侧(RTL 阅读顺序中自然的末尾)。进度条从右向左填充。[crowdin]
10.D ARIA 网格键盘交互规范(任何包含可聚焦单元格的自定义网格/表格都必须遵循)
- 循环 tabindex:恰好一个单元格的
tabindex="0",其余单元格均为-1;网格容器本身永远不能成为 Tab 停靠点。 - 方向键在单元格间移动,到达边界时停留(不在行/列边缘循环)。
- Home/End 在当前行内移动;Ctrl+Home / Ctrl+End 跳转到网格的首个/最后一个单元格。
- Page Up/Down 仅用于可滚动网格(移动约 5 行)。 [w3 grid data-grids] 如果使用真正的设计系统 Table(Carbon、Fluent、Polaris、Ant),这些行为已由其处理,无需重新实现。只有在手动构建网格时才采用此规范。
10.E 跨场景无障碍最低要求
- 触控目标 >= 44-48px(触控),>= 24px(密集桌面端)。[material3 accessibility]
- 使用
:focus-visible,绝不使用会在鼠标点击时闪现的裸:focus;移除焦点轮廓时必须提供替代方案。 - 所有纯图标控件都必须有
aria-label。 - 数字/日期/货币格式应适配区域设置;绝不能使用含糊的
01/02/2025。
11. 引导与渐进式信息呈现
内容密集的产品容易在首次使用时令人不知所措。逐步展现强大功能,让复杂性随用户需求而来。
渐进式信息呈现(默认机制):
- 将高级控件(批量编辑、列管理、已保存视图、API/webhook 配置)放在一次交互即可访问的位置(“更多”菜单、展开面板、设置弹出框),不要一次性全部展示在屏幕上。Linear 的原则:简单易上手,功能逐步增强。[linear]
- 经验法则:某个区域内控件 >7 个时,应触发信息收起。 将主要的 3-5 个操作放在行内,其余操作收进溢出菜单;通过带标签的展开面板展示高级设置,绝不要用没有工具提示的神秘齿轮图标。
- 信息收起应针对具体界面,而非全局生效:不要隐藏用户两次点击前刚用过的控件。
首次运行(必须):
- 首次运行时应显示**“从未使用过”空状态**(第 6.C.1 节),而不是空白网格或加载动画。用一句话说明此界面的用途,并指向唯一的首个操作(“创建你的第一个项目”),而不是列出十个选项的菜单。
- 仅当示例/演示数据明确标注且可通过一次点击删除时,才使用这些数据;绝不能留下用户无法区分真实数据的虚假“Acme Inc”行。
引导标记、产品导览、检查清单(除非可关闭,否则禁止):
- 任何引导标记、产品导览或入门检查清单都必须允许用户在第 1 步跳过,并可永久关闭;关闭状态应按用户跨会话保留(绝不重新显示已关闭的导览)。无法跳过的导览,或没有关闭入口的检查清单,都属于明显缺陷。
- 同一时间最多进行一个导览;绝不要在模态框上叠加引导标记。
- 导览应指向真实界面中的真实控件;绝不要使用虚假的高亮截图。退出时应让用户回到可用状态,而非死胡同。
- 按用户群体区分:回访用户/有经验的用户和受邀加入的队友应跳过首次运行流程;不要让加入已有数据的工作区的用户重新走一遍引导。
**从空状态到高效使用的路径:**每个空状态的 CTA 都必须能在 <=3 步内引导用户完成真正有价值的操作。仅链接到文档的入门检查清单只是装饰。
12. 上下文感知的主动性(需要识别的规范骨架)
当产品描述符合以下任一模式时,应搭建完整骨架,而非片段:
- 索引/表格页面:标题 + 工具栏(搜索、筛选器、主要操作、密度)+ 已应用的筛选条件标签 + 表格(固定表头、选择功能)+ 分页页脚 + 三种空状态 + 错误状态。
- 列表-详情/主从布局:列表区域 + 检查器 LayoutPanel,在约 1024px 以下时叠加显示 +“未选择内容”空状态。
- 设置页面:分区导航 + FormLayout 面板 + 自动保存或保存栏 + 仅在危险操作区域使用 Card。
- 多步骤向导:真正的步骤指示器 + 线性步骤 + 每步验证 + 审核步骤 + 非破坏性的退出方式。
- 仪表板/概览页面:KPI 磁贴行(Cards)+ 小组件网格 + 一到两个可深入查看的表格(行)。
- 带批量操作的管理控制台:表格 + 持久化选择 + 批量操作栏(最多显示 5 项,其余收纳至溢出菜单)+ 确认对话框。
分层概览的语义缩放(投资组合、路线图、组织架构图):放大某个方框时,应切换为更丰富的表示形式,同时让面包屑、缩略导航图和统计面板重新聚焦于当前视图。层级、输入、无障碍要求和 DOM 实现方案:references/semantic-zoom.md。
内部 UI 规则(两层结构、使用通俗词汇、不使用禁用控件、不使用 … 菜单、不换行的控件、不使用省略号、不带条纹的列表、带标签的状态图标、三部分式错误、原生呈现外部对象):审核任何产品 UI 时都应加载这些规则;若与上方的实现方案冲突,以这些规则为准。
完整的组件实现位于宿主系统中,不在此处重复:通过宿主系统的模板/骨架命令或其文档获取(references/canonical-sources.md)。references/block-skeletons.md 中详述了三个高价值骨架,因为它们体现了最常出错的约定;请将其视为规范形态,并适配宿主系统。
13. 性能与无障碍护栏
- 表格加载和筛选的响应应让人感觉即时;超过约 1s 时,应显示乐观反馈/加载反馈,超过 6.B 中的阈值时,应升级处理。
- 企业级网格性能:渲染时不要为每行分配样式/菜单对象;限定自动调整尺寸的范围;除非已做记忆化处理,否则使用
columnResizeMode:"onEnd";超过约 100-200 行时进行虚拟化。 - 产品 UI 中的动画仅用于反馈:状态转换、焦点移动、行的进入/离开。不要使用装饰性滚动动画、跑马灯或视差效果。所有动画都应遵循
prefers-reduced-motion。 - Core Web Vitals 仍然适用:CLS < 0.1(骨架屏必须预留实际尺寸,避免内容跳动),交互时 INP < 200ms。
14. 暗色模式
由令牌驱动,整个应用锁定为一种主题(遵循 taste-skill 第 8/11 节的规范:应用中的任何区域都不得中途切换为反色主题)。使用宿主系统的语义层令牌(var(--color-*)、Carbon $layer/$border-subtle 等),不要硬编码灰色。两种模式下都要测试表格、冻结列(纯色背景必须是主题令牌,否则会在暗色模式下透底)、覆盖层和焦点环。在外壳根节点设置一次主题,不要按分区设置。
15. 隐形细节(微工艺底线)
复合规则:这些问题单独来看都不会毁掉界面,但全部累积起来,就会拉开精致与粗糙之间的差距。 用户很少会指出具体遗漏——他们只会意识到“这里都没做好,其他地方大概也不行”。这里的所有问题修复成本低,但发现得越晚,代价越高;因此应将此列表视为底线,而非进阶目标。[隐形细节工艺说明]
15.A 几何与光学校正
- 嵌套边框圆角公式:
inner radius = outer radius - gap。当间距 >= 外层圆角半径时,内层元素无需圆角。内外层圆角半径相同,总会显得过圆。 - 优先光学对齐,而非数学对齐:数学上相等的内边距看起来可能不对。视觉重心有方向性的字形需要微调——播放三角形应向真正的中心右侧偏移,勾号应向下偏移。相信眼睛,而非数字;对齐视觉重心,而非边界框。
- 图标与文本对齐:将图标包在
height: 1lh的盒子中,并与行框顶部对齐,不要让它相对于多行文本块垂直居中。标签换成两行时,居中对齐就会失效。 - 垂直节奏:侧边栏中的每个图标、控件和行操作元素都应位于一致的基线上。单个元素偏移 1-2px,就是经典的“哪里不对劲”信号。
- 子菜单对齐:下拉菜单的首项应与触发器的高度精确对齐,这样打开菜单时锚点在视觉上不会发生偏移。
15.B 排版与文本流
- 在正文上使用
text-wrap: pretty,避免末行只剩一个词;在标题和短标签上使用text-wrap: balance,让行长更均衡。对于段落,设置以ch为单位的max-width通常比balance更好,后者可能会造成块状参差不齐的效果。 - 截断策略因内容类型而异(扩展第 5 节):正文换行,标签在末尾截断;文件名、路径和 URL 在中间截断,因为区分信息可能在末尾(
report-final-v3....xlsx毫无用处;report-fi...v3.xlsx则有用)。 - 紧密组合的词语绝不跨行断开。 在数值与单位之间使用不换行空格(
20 ms、4 GB),在键盘快捷键内部(Cmd + K)以及复合产品名称中也要如此。数值落在一行、单位却跑到下一行,就是一个明显的坏信号。
15.C 数字(扩展第 5.F 节)
- 只显示有意义的位数,并根据后果决定保留多少位。 仪表盘上用
52.4%合适;账单中必须写10.30,因为10.3看起来像是不同的金额。根据后果决定精度,然后在整列中保持一致。
15.D 点击目标(扩展第 10.E 节)
- 扩大目标区域,而非视觉元素。 一个 20x20px 的控件可以通过具有负值
inset的伪元素,将点击区域扩展到约 26x36px——像素尺寸保持不变,可点击区域则不会。 - 扩展到间距处为止。 在没有其他目标的方向上可以自由扩展;在存在相邻元素的方向上,扩展到间距处即止,避免两个目标的空间发生重叠。
- 相邻的可点击元素之间保留 4-8px 间距。 即使目标尺寸符合要求,零间距的相邻元素也会导致误点。
- 标签和间距也是控件的一部分:复选框的描述文本以及方框与文本之间的空隙都应可点击,不能只有 16px 的方框可点。
15.E 隐形默认设置(成本低,始终启用)
- 在装饰性图形、图标和界面框架元素上设置
user-select: none,避免拖选时家具般的界面元素被一并高亮。绝不要对用户可能会复制的实际内容这样做。 - 为每个输入框设置正确的
type=,让移动设备显示合适的键盘:type="tel"、type="email"、type="url"、inputmode="numeric"。 - iOS 输入框的
font-size必须 >= 16px,否则 Safari 会在聚焦时缩放视口。这是一个只有在真实设备上才会显现的布局问题。 - 每个
<video>都要设置playsinline,否则 Safari 会在播放时强行切换到全屏。
15.F 传达溢出状态(扩展第 5 节)
- 截断最后一个可见项。 部分可见的行或卡片是区域可滚动的最明显信号。容器边缘与内容整齐齐平,会让人觉得内容已经结束。
- 让
overscroll-behavior/ overscroll 背景与页面背景一致,这样橡皮筋滚动时就不会露出不协调的颜色。 - 在隐藏滚动条的水平滚动区域使用边缘渐变——渐变是仅剩的视觉提示。
15.G 抑制动画(扩展第 13 节)
prefers-reduced-motion会禁用所有动画:滑动、标签交叉淡入淡出、颜色过渡、悬停效果。内容仍会立即变化,不会因此变得无法访问。- 当状态变化速度快于动画时,抑制动画。 如果用户切换步进器、标签集或筛选器的速度快于每次变化约 120ms,过渡就会叠加并卡顿。检测到快速变化时,应切换为即时切换——卡顿的动画比没有动画更糟。
- 方向表达层级。 在任何顺序流程中,前进和后退都应沿相反的方向移动(前进时内容和标题向上滑动,后退时向下滑动)。这种一致性无需工具提示,就能让用户知道“我将前往哪里”。工具提示只是仅供鼠标用户使用的权宜之计;应优先使用动画、
aria-current和标签。
16. 产品 UI 中 AI 的破绽(禁止模式)
与 taste-skill 的落地页破绽不同。这些是模型仿造产品界面的典型特征。除非需求确实要求,否则一律禁止。
- 卡片汤:每条记录都包在 Card + Badge 中。应使用行。(第 0.C 节)
- 导致页面横向滚动的固定宽度
<table>:不要使用拥有滚动行为的包装容器。(第 4 节) - 截断的按钮标签(“保存更改...”)。应按内容调整尺寸。(第 5.C 节)
- 使用仅悬停时显示的
title截断标识性内容(名称/ID/错误)。(第 5.A-B 节) - 在包含 5 个或更多字段的表单中,仅用
disabled提交按钮做验证。(第 8.C 节) - 在首次使用、筛选后无结果和加载失败时重复使用同一个通用空状态。(第 6.C-D 节)
- 只显示“发生错误”,不提供原因、操作或代码。(第 6.D 节)
- 永不结束的骨架屏,或无法反映真实布局的纯灰色框架。(第 6.B 节)
- 在缓存内容的后台刷新时显示整页加载动画(导致用户正在查看的数据再次变空白)。(第 6.B 节)
- 带透明背景的粘性/冻结列(滚动时内容会透出来)。(第 7.B 节)
- 随意设置 z-index(
9999、99999),而不采用分层的令牌尺度。(第 9.D 节) - 在德语/芬兰语文本扩展时会被裁切的固定宽度徽标/标签/选项卡。(第 4、10.B 节)
- 对本应可通过键盘访问的菜单项/选项卡使用原生
disabled(应使用aria-disabled)。(第 9.C 节) - 把徽标当作装饰,以及在每一行滥用状态圆点(仅在需要时谨慎显示状态)。
- 嵌套元素的内外边框半径相同(总会显得过于圆润)。(第 15.A 节)
- 数值与单位被换行拆开,或快捷键组合被从中间拆开。(第 15.B 节)
- 20px 图标按钮的点击区域也只有 20px,或两个可点击元素紧贴在一起。(第 15.D 节)
- 用工具提示承载键盘或触屏用户需要的信息(这只是仅供鼠标用户使用的权宜之计)。(第 15.G 节)
- 可滚动区域的末端与内容齐平,没有截断项、渐变或其他视觉提示。(第 15.F 节)
- 快速点击时过渡动画叠加并卡顿(未抑制快速变化)。(第 15.G 节)
- 在工作工具中加入纯装饰性动画(装饰性的滚动/悬停动画)。(第 13 节)
- 虚假的步骤标签(“阶段 1 / 阶段 2”)、虚假的精确数字,以及已发布界面中的“张三”/“某某公司”占位数据。
- 任何可见位置都不得出现 em dash(
—)。 标题、单元格、标签、空状态/错误文案、工具提示,以及作为“未设置”单元格占位符时都禁止使用。请使用连字符(-)。这是违反次数最多的单项破绽;绝不容忍。(taste-skill 第 9.G 节、内部规则)
17. 参考词汇(Agent 应了解的名称)
应用外壳 / PageLayout(页眉 / 内容 / 面板 / 侧边栏 / 页脚)· 标准布局(列表-详情 / 辅助面板 / 信息流)· 主从布局 + 检视器 · IndexTable / 资源列表 / DataGrid / 表格 · 冻结(固定)列 · 虚拟化 · roving tabindex · 层叠上下文 · 容器查询 · 骨架屏(形状镜像)· Blankslate / EmptyState / ErrorState · 溢出 / 优先级加号 · 已应用筛选条件标签 / 分面搜索 · alertdialog · 乐观更新与悲观更新 · 密度模式 · 伪本地化 · 命令面板 · 工作区切换器 · 作业/状态托盘。
18. 重新设计流程(审查现有的高密度屏幕)
在调整视觉风格之前,先审查那些比美观更重要的正确性问题:
- 滚动归属违规(页面横向滚动;嵌套的双重滚动)。
- 主要内容被截断;仅在悬停时提供备用内容。
- 状态区分缺失(三种状态共用一个空白框;没有错误状态;缺少骨架屏)。
- 冻结列内容透出;粘性背景透明。
- z-index 随意处理;嵌套层叠上下文导致的分层问题。
- 缺少 i18n 缓冲空间;文本容器宽度固定。
- 键盘操作/a11y 缺陷:没有 roving tabindex、只有裸露的
:focus、图标按钮没有标签、禁用提交是唯一的提示。 - 微观工艺底线(第 15 节):嵌套圆角、光学对齐、粘连单位、扩大点击目标、默认状态不可见、溢出提示。 先修复这些问题,然后再优化密度、设计令牌和视觉效果。保留宿主设计系统;不要把 Carbon 换成 shadcn 来“重新设计”。
18.A 犹豫测试(如何发现问题,而不只是修复问题)
难点在于发现问题,而不是修复问题。 大多数问题一旦发现,几分钟就能修好,因此要把精力花在观察上。亲自操作界面,把每次停顿都当作缺陷报告。四种迹象及其固定诊断如下:[不可见细节工艺笔记]
| 观察到的情况 | 含义 |
|---|---|
| 同一个按钮被第二次点击 | 没有加载状态或没有反馈——用户以为第一次点击没有生效 |
| 光标游移(移动却未落定) | 点击目标太小,或用户无法辨认哪个选项才是他们想要的 |
| 下意识撤销(操作后立刻按 Cmd+Z) | 操作没有确认实际产生的效果 |
| 反复重读标签 | 文案有误,而不是布局有问题 |
用同样的方式仔细研究你欣赏的界面——放慢速度,观察而非使用。摩擦就是教训。
18.B 压力测试矩阵(刻意引入阻碍)
只在快速 Wi-Fi、简短英文字符串和数据已填充的情况下测试过的界面,等于未经测试。判定界面完成前,逐项执行:
- 将网络限速到 3G,或注入约 800ms 的 API 延迟,观察屏幕在这段间隔中的表现。
- 使用真实长度的内容,再加上扩展长度较长的语言环境(DE/FI)和一种非拉丁文字。
- 使用真正的空数据——测试全部三种空状态(第 6.C 节),而不只是其中一种。
- 强制 API 失败,并测试部分失败(一共六个小组件,其中一个失败)。
- 全程只用键盘、不用鼠标,完成主要任务。
- 在所有尺寸类别之间调整窗口大小,包括那些尴尬的中间宽度。
- 使用实体设备,而不只是 DevTools 模拟(iOS 输入缩放和
playsinline只会在实体设备上出现——第 15.E 节)。 - 在每个字段中粘贴内容:将多行文本粘贴到单行字段、粘贴带格式文本,以及粘贴含前后空白的值。
18.C 将发现记录为 Break / Expectation / Direction
每条观察记录为三行,不多不少——足以指导行动,也足够简短,便于实际记录:
- Break - 准确说明出了什么问题、发生在哪个界面,以及在什么条件下发生。
- Expectation - 说明原本应该发生什么。
- Direction - 用一句话概括通用修复方案,而不是只针对这次情况打补丁。
将反复出现的发现提炼为规则,并按功能而非外观归类:“颜色输入框接受任何格式,并将其规范化为十六进制”具有复用价值;“颜色输入框看起来像 Linear 的”则没有。
19. 与 taste-skill 的关系 + 不在适用范围内
- 移交给
taste-skill:营销主视觉区块、落地页、作品集、编辑内容、插画丰富的品牌界面。如果一个屏幕一半是产品、一半是营销内容,就拆分处理:营销区块交给 taste-skill,产品界面交给本技能。切勿在同一个组件上同时运行两者。 - 移交给
dataviz:图表创作(颜色、编码、坐标轴)。本技能涵盖表格和图表周围的仪表盘框架,不涉及图表内部。 - 不在适用范围内:原生移动端(iOS/Android)应用界面(超出响应式 Web 的范围,例如标签栏、原生导航)——这属于其他技能的工作;请标记出来,不要悄悄用近似方案代替。实时协作基础功能(在线状态、光标、OT)——属于不同类别的问题。
20. 最终飞行前检查
逐项检查。任何一项未通过,都说明界面尚未完成。
- 产品定位是否明确(第 0.B 节的一句话)?
- 是否根据定位设定 调节项(DENSITY / DATA_COMPLEXITY / CONSEQUENCE),而非默默采用基线值?
- 是否先按 px 规划 框架预算,再放置内容;是否为外壳和地标区域命名(Header/Content/Pane/Sidebar 不混为一谈)?
- 密集数据是否使用 行而非卡片;Card 是否仅用于组件;是否避免卡片泛滥和嵌套卡片?
- 滚动归属:每个轴是否恰好只有一个滚动包装器;页面是否从不水平滚动;粘性定位是否限定在包装器内?
- 每个会截断文本的 flex 子元素上是否都有 min-width:0?
- 是否 不截断主要内容(名称/ID/错误/表头)和 按钮标签;每个截断内容是否都有可通过焦点访问的完整文本备用方案?
- 行数限制方案是否完整(全部 4 个属性,受限节点上无内边距)?
- 每个数据区域是否都设计了 四种状态(理想/加载/空/错误)?
- 加载阈值:<1 秒不显示内容;骨架屏与布局一致;>10 秒显示确定进度;刷新缓存时绝不清空内容?
- 是否区分 三种空状态(首次运行 / 筛选后为空 / 零即成功);加载失败是否显示包含原因、操作和代码的 ErrorState?
- 是否处理 部分失败/过期/离线状态(单个组件失败时仅在原处显示失败;刷新缓存时绝不清空内容;离线时排队或阻止操作并显示提示;乐观更新有回滚路径)?
- 内容边界情况:计数是否按 0/1/N 正确使用单复数(不出现“1 items”);大数字是否缩写但可访问精确值,且在精度重要时绝不缩写;缺失数据是否使用与零/空值不同且一致的占位符?
- 是否考虑了 权限/套餐状态(存在角色/层级时,区分禁用与隐藏、只读、套餐锁定)?
- 表格:是否有负责滚动且高度有界的包装器;是否有粘性表头;冻结列是否使用不透明的主题令牌背景、内嵌阴影分隔线和 z-index;虚拟化是否采用双包装器 DOM?
- 是否正确选择 分页还是无限滚动(超过 50 条或用于搜索时分页;无限滚动仅用于 Feed,并搭配 pushState + 移动端平铺批次)?
- 选择项是否跨页/排序/筛选保留;批量操作工具栏是否最多显示 5 项,超出部分收纳起来?
- 存在筛选器时是否显示 已应用筛选条件标签行;搜索和筛选是否是不同控件?
- 表单:是否默认使用顶部标签;是否在失焦和提交时验证;是否显示行内错误和错误摘要;提交失败时是否保留输入内容;字段数达到 5 个或更多时,是否避免仅用禁用提交按钮作为提示?
- 异步验证是否经过防抖处理并通过 AbortSignal 取消?
- 自动保存是否按组进行,且不用于密码/权限/财务字段;是否显示 Saving/Saved 状态,并在失败时提供持久的重试入口?
- 破坏性操作确认是否将焦点放在安全操作上,使用
role=alertdialog,并在关闭时恢复焦点? - 对话框是否锁定并恢复焦点、支持按 Escape 关闭且包含标题/描述;提示框是否在悬停时暂停并可关闭?
- z-index是否来自分层令牌尺度(不使用 9999);层叠问题是否通过排查层叠上下文解决,而非增大数值?
- 每个文本容器是否预留 i18n 缓冲空间(德语增加 30-40%);是否避免文本使用硬编码固定宽度;比较数字是否使用 tabular-nums?
- 如果 RTL 在范围内,是否仅镜像方向性图标?
- 键盘操作/无障碍:自定义网格是否使用 roving tabindex;是否支持
:focus-visible;图标按钮是否有标签;可通过键盘访问的菜单项是否用aria-disabled而非disabled;点击目标是否为 44px/24px? - DENSITY 要求时是否提供 密度切换(切换模式,而非缩放)?
- 深色模式是否锁定为单一主题;冻结列背景是否使用主题令牌;是否在两种模式下都经过测试?
- 微观打磨基线(第 15 节):嵌套圆角是否为外层圆角减去间距;方向性字形是否采用视觉居中(而非数学居中);是否使用
text-wrap: pretty/balance;路径和文件名是否从中间截断;单位和快捷键是否通过 nbsp 连在一起;是否使用负值inset的伪元素扩展点击目标,且相邻目标间距为 4-8px;标签及其间距是否均可点击? - 不可见默认设置(第 15.E 节):
user-select:none是否仅用于装饰内容;输入框的type=/inputmode是否正确;iOS 输入框字号是否 >= 16px;视频是否设置playsinline? - 是否明确告知内容溢出(第 15.F 节):最后一项是否被截断;过度滚动背景是否匹配;隐藏滚动条时边缘是否渐隐?
- 是否正确抑制动画(第 15.G 节):
prefers-reduced-motion是否禁用所有动画;快速状态变化(<~120ms)是否改为即时呈现;前进/后退是否沿相反方向移动? - 是否运行 犹豫测试(第 18.A 节),并走查 压力测试矩阵(第 18.B 节:限速网络、长文本/非拉丁文本、空状态、失败、仅键盘操作、调整尺寸、真实设备、粘贴)?
- 项目规范:是否从真实文档中发现属性(不凭空编造);是否不用
<div>进行布局;是否仅使用设计令牌;Badge 是否仅用于计数;状态是否通过状态指示器呈现? - 是否不存在第 16 节所述的 产品 UI 痕迹;所有可见内容中 是否完全不使用 em dash?
- 每个应用是否仅使用 一个设计系统(不混用 Carbon + Fluent)? 如果有任何一项无法如实勾选,请先修正,再交付。
参考资料
references/house-ui-rules.md- 产品 UI 的内部规范:两个层级、直白措辞、控件、列表、文本溢出、聊天回答块、外部对象、设置和错误。references/semantic-zoom.md- 分层概览中随缩放级别变化的细节。references/block-skeletons.md- 三种最常出错的区块契约的参考实现(索引 + 详情抽屉框架、表格滚动/粘性表头/冻结列 CSS 契约、截断契约)。references/install-commands.md- 附录 A:各主机设计系统对应的包管理器安装命令。references/canonical-sources.md- 附录 B:经 gsearch 验证、用于支撑本技能规则的主要来源 URL,按主题分组。