加载中和没有数据,是两件事

视觉上它们都是"一片空白",但含义完全相反。前者是"在动",后者是"没东西"。 混成一个,用户会以为系统坏了 —— 而他可能只是等了三秒。

企业分析工作区 331 实体 · 861k 条财务记录

切换时注意两件事:只有数据行在动,页头和工具条不动(布局稳定); 从"出错"点回"有数据",内容出现的位置正好是它原来的位置。


每态都必须给出口

只描述现状不给动作,等于把用户卡死在那儿。这是四态里最容易漏的一条。

空 —— 必须说明为什么是空的,并给下一步。 "还没有导入任何公司" 比 "暂无数据" 有用得多:前者告诉用户他在哪、缺什么。

出错 —— 必须能恢复。只写"出错了"是死路,而且刷新往往解决不了问题 (参数错了、文件格式不对)。要告诉用户哪里错了、怎么改。

成功 —— 是过渡态不是停留态。普通操作的成功反馈用按钮的 data-state="success"(1.2 秒自动消失),别做成一个整页。


骨架屏还是 spinner:看结构是否已知

结构已知 · 骨架屏

知道会来"标题 + 三行 + 一张图",就照这个结构画占位。 价值不在好看,在于内容到位时不跳动。

结构未知 · spinner

🔴 别在结构未知时用骨架屏。画出来的结构跟实际不一致, 内容到位时布局会剧烈跳动 —— 那是在撒谎。


列表项的骨架:头像占位 + 两行文字

skeleton--avatar 是 40×40 的全圆占位,专门配 skeleton--text 做「头像 + 姓名 + 副标题」这种列表项。宽度节奏要跟真内容一致, 否则内容到位时头像会把文字挤走 —— 那正是骨架屏要避免的事。


300ms:不显示比闪一下好

请求在 300ms 内返回时不要显示加载态。骨架屏闪一下比不显示更糟 —— 那是一次无意义的闪烁,反而让人觉得卡。正确做法是延迟 300ms 再显示(约 10 行 JS)。


reduced-motion 下,两个动画处理相反

同样是"加载中的动画",一个保留一个去掉。判断依据是去掉后是否损失信息:

spinner 保留但放慢 —— 它停了用户会以为页面卡死。 reduced-motion 针对的是大幅位移(前庭功能障碍),小幅度原地旋转不属于此类。

骨架屏扫光停止 —— 静态占位块仍然传达"这里有内容正在来", 不损失信息;而扫光恰好是横向位移,正是要减少的那类。

对比:错写法 vs 本库写法

❌ 常见写法:empty 态没有出口

说明写得很清楚,然后呢?用户读完只剩一个动作:刷新,或者退出。一条"没有数据"的提示不构成一个状态,它只是死路。

✅ 本库:empty 态必须带动作

state__action 是 empty 态的一部分:要么给一条能走的路,要么别显示这个空状态。