关于 states
states 是面向界面设计与实现的技能,指导团队在构建数据驱动区域前枚举加载、空、部分、错误、离线和成功等状态。内容涵盖状态呈现、转换、重试、乐观更新与未保存数据处理。
主要功能
- 01枚举数据区域的界面状态
- 02设计骨架屏与加载时序
- 03区分空状态和筛选无结果
- 04处理错误、重试及陈旧数据
- 05指导乐观更新与未保存内容
- 06规划状态间的视觉转换
适用场景
- 01设计首次加载和刷新期间的页面
- 02为没有内容或筛选无结果的区域编写界面
- 03规划网络错误与离线时的数据呈现
- 04处理保存失败、乐观更新和未保存更改
安装
复制下面的命令,在终端中运行。
npx skills add https:/ / github.com/ paragonhq/ stealth-skills --skill states安装后在 Claude Code 中输入/states确认已加载。
详细介绍
状态
顺畅路径只是六种状态之一。界面的好坏取决于另外五种,因为用户会在这些状态下卡住、等待或担忧。
构建前先列举
对于每个依赖数据的区域,列出以下状态:
| 状态 | 时机 | 显示内容 |
|---|---|---|
| 首次加载 | 尚无数据 | 最终布局中的骨架屏 |
| 再次加载 | 已有数据,正在刷新 | 保留数据,显示轻微提示 |
| 空状态 | 请求成功,但无内容可显示 | 一句话,一个操作 |
| 部分内容 | 只有部分数据,或仅有一页数据 | 显示已有内容,并清楚标明边界 |
| 错误 | 请求失败 | 说明发生了什么、该怎么做,以及重试选项 |
| 数据过期/离线 | 数据可能已过期 | 显示数据,并标明状态 |
| 成功 | 操作已完成 | 在原处安静地显示 |
如果一个区域只处理其中两种状态,那它就还没完成。
加载
骨架屏
- 尽可能与最终布局完全一致:高度、宽度和节奏都相同。骨架屏被不同形状的内容替换,比显示旋转指示器更糟。
- 预留空间。内容到达时,页面上的任何元素都不应移动。
- 使用
--hover级别的灰色块,并沿用元素的圆角。不使用闪烁效果;或使用缓慢的 2–2.5s 低幅度脉冲效果。快速闪烁会让等待显得更久。 - 显示三到五行骨架屏,而不是铺满整个屏幕。用户需要了解内容的大致形状,而不是数量。
- 内容使用骨架屏,操作使用旋转指示器。
时间
- 前 150–200ms 不显示加载状态;大多数请求会在这段时间内完成,而一闪而过的提示反而更糟。
- 加载状态一旦显示,至少保持 300ms,避免闪烁。
- 超过 3 秒时,给出提示,例如“仍在处理中……”或进度估算。
- 超过 10 秒时,提供退出方式:取消,或提示“我们会通知你”。
重新获取数据
已有数据时,绝不要用骨架屏替换它。保留数据,并在区域附近显示刷新提示(例如标题中的淡色旋转指示器或细微的进度条);如果刷新很快,也可以完全不显示提示。数据消失又重新出现,是页面最常让人感觉出错的原因。
图片
声明 width 和 height(或 aspect-ratio),让图片加载前就占好位置。加载后用 200ms 淡入。首屏以下的图片使用 loading="lazy"。加载失败时,显示同样大小的中性占位图,绝不显示损坏图片图标。
空状态
空状态是引导用户入门的时机,包含:
- 一句话说明这里会显示什么(“还没有项目”)。
- 一个能填充内容的操作(“创建项目”),并将其设置为主按钮样式。
- 可选的一句话,说明其重要性或内容如何填充。
空状态应位于所描述的区域内,尺寸与该区域一致,而不是覆盖整个页面。除非产品有插画系统,否则不要使用插画;单独一个卡通宇航员看起来就像模板。
区分空状态与筛选后无结果:“没有符合 foo 的结果”,并附带清除筛选条件的操作,和“还没有项目”是两种不同的状态。
部分内容
- 分页:显示边界。“共 1,240 项,当前显示 50 项”,并提供加载更多的方式。无限滚动需要显示末尾,并提供到达页脚的方式。
- 渐进式加载:有多少就先呈现多少,不要等待最慢的请求。
- 流式列表:在末尾追加内容,不要移动用户正在阅读的内容;视口上方有新项目时,用“新增 3 项”胶囊提示,而不是移动整个页面。
错误
错误提示按以下顺序说明三件事:
- 用通俗的语言说明发生了什么(“无法保存更改”)。
- 说明原因(如果能帮助用户采取行动)(“你已离线”“该名称已被占用”)。
- 告诉用户该怎么做(“请重试”“请选择其他名称”),并明确给出操作。
位置:尽可能靠近问题根源。字段错误放在字段下方。保存错误放在保存按钮旁边。区域错误放在区域内。页面错误放在页面上。对于需要用户修复的问题,绝不使用 toast 提示。
绝不显示堆栈跟踪、单独的状态代码,或没有后续操作指引的“出了点问题”。刷新失败时,保留屏幕上的数据;将其标记为过期数据,而不是清空。
网络短暂中断时,自动静默重试一次。之后由用户决定是否重试。
乐观更新
对于切换、重新排序、重命名、点赞以及失败率较低的操作:
- 在同一帧中更新界面。
- 发送请求。
- 失败时:还原更改,并在失败位置说明原因。
为乐观更新的项目保留稳定 ID,以便后续重新获取数据时能够合并,而不是重复创建。对于涉及金钱、无法撤销的删除,或用户难以轻松察觉还原结果的操作,不要使用这种方式。
过期数据与离线状态
- 可能已过期的数据应标记出来(“4 分钟前更新”),而不是隐藏。
- 离线时:显示持续、低调的提示栏,而非模态框。读取操作继续使用缓存;写入操作排队,并告知用户。
- 连接恢复后,同步数据并清除提示栏。不要重新加载页面。
未保存的更改
- 显示状态:在标签标题上显示圆点,并在操作附近显示“已保存”/“正在保存…”/“未保存”。
- 保存错误的代价较低时自动保存;否则由用户明确点击保存。
- 仅在有内容可能丢失时,才提示用户离开页面,并说明将丢失什么。
- 绝不丢失用户输入。从本地存储恢复草稿。
成功
- 在原处显示成功状态。按钮显示“已保存” 1.5 秒,行内容随之更新,计数增加。
- 仅当效果发生在其他位置时才显示 toast(“已添加到 阅读清单”)。
- 仅对一次性事件使用全屏成功提示:注册、付款、完成引导流程。
- 绝不说“成功保存成功”。直接说明更改了什么。
状态转换
- 骨架屏过渡到内容:以 200 毫秒交叉淡入淡出,不移动内容。
- 内容过渡到错误:在原处替换,不抖动。
- 空状态过渡到首个项目:项目进入,空状态消息退出。不要让两者同时动画。
- 用户操作触发的状态变化应快于服务端触发的变化。
检查清单
- 每个加载数据的区域都包含骨架屏、空状态、错误状态和重新获取数据时的状态
- 数据到达时,页面上没有任何内容移动
- 加载时间短于 150 毫秒时不显示加载指示器;一旦显示,至少持续 300 毫秒
- 重新获取数据时,屏幕上保留现有数据
- 空状态只提供一个操作
- 错误提示说明该怎么做,并放在问题根源旁边
- 乐观更新失败时会还原,并附上说明
- 过期数据会标记出来,而不是隐藏
- 成功状态在原处显示
来自目录的资源
- Page Flows — 来自已上线产品的引导流程、空状态和错误流程录屏。
- Nielsen Norman Group — 响应时间限制及其背后的研究。
- Mobbin — 搜索“空状态”和“错误”,查看真实应用如何处理这些情况。
- Smashing Magazine — 关于骨架屏和感知性能的深度文章。
- 更多内容请访问 stealth.pm/look。