Cover All Four States
A piece of interface shows four faces in the real world, and the AI usually builds only one of them. The other three are either blank or shove the error straight at the user.
- 自己演示一切正常,同事一登录就是白屏,因为他账号里没数据
- 网慢的时候页面先塌下去,数据回来又撑开,正要点的按钮跑掉了
- 接口一挂整页变成 Uncaught TypeError,用户只能刷新碰运气
The cause isn't the model. You say "build an order list" and the picture in its head is a screen of neatly arranged orders, because that's all your description contains. Empty, loading, and error never appear in the spec — they only appear on real users' hands: a brand-new sign-up opening it for the first time, the signal dropping on the subway, the backend happening to be mid-deploy.
Filling them in usually costs a few dozen lines of code. The real hurdle is treating them as part of the spec when you write it, not patching them in after the bug reports arrive.
How to Tell
Any time you fetch data, the result lands in one of four cells. Walk through this diagram and you'll know which one you missed.
Design Considerations
- The empty state is a chance to introduce the product — don't just write "no data." The first thing a new user sees is usually the empty state. That space should explain what will live here and how to put something there: one line of explanation plus one primary button. A four-character "no data" wastes the best teaching spot on the page.
- Use skeleton screens for loading, not a centered spinner. A skeleton screen reserves the space content will fill, so the page doesn't jump when data arrives. A spinner lets the whole area collapse and re-expand. Only show a loading state for waits longer than a second — shorter flashes feel worse than no indicator.
- Write error copy as "state the situation + next action." First say what happened (couldn't reach the server), then what the user can do (retry or check the network). This pattern works for any hint. Put the retry button next to the copy; don't make people refresh the whole page.
What form the hint takes depends on how serious the matter is. Three tiers from strong to weak — skipping a tier means the user quickly learns to click "OK" with their eyes closed.
- 局部失败就局部提示,别让整页塌掉。 侧边栏的推荐模块挂了,不该导致整个页面变成错误页。状态的粒度要跟数据源的粒度对齐,每个独立请求各自管好自己那块区域。
- 四种状态的容器高度尽量接近。 切换时高度剧烈变化会导致页面跳动,用户正要点的按钮会跑掉。给容器一个 min-height,是成本最低的体验改善。
- 错误文案里不要用感叹号,句尾也不加标点。 感叹号会把一次网络波动渲染成事故。等待类文案统一用省略号收尾(加载中…),这是中文界面里比较通行的写法。
Note for the AI
Put it in the component spec, or sink it into the project's CLAUDE.md, and every data component generated from then on will carry all four states by default.
凡是涉及异步数据的组件,必须同时实现四种状态,缺一不可: 1. 正常态:有数据时的正常渲染。 2. 空态:数据为空数组时。区分两种来源—— - 用户还没有任何数据:给一句说明 + 一个主行动按钮 - 筛选/搜索后无结果:提示放宽条件 + 一键清除筛选 3. 加载态:使用骨架屏,形状和数量贴近真实内容,不要用居中 spinner。 4. 错误态:按「说明当前状况 + 引导措施」写文案,配一个「重试」按钮, 不要暴露状态码或堆栈。 文案规范: - 不使用感叹号;句尾除疑问句外不加标点。 - 等待类文案统一以「…」结尾,例如「加载中…」。 - 提示强度分三档:必须立刻处理用弹窗,状态变化用全局横幅, 可看可不看用气泡。不要越级。 其他约束: - 四种状态复用同一个外层容器,设置 min-height 避免切换时页面跳动。 - 错误只影响当前组件,不要向上冒泡导致整页变成错误页。 - 把状态做成组件的一个 prop('ok' | 'empty' | 'loading' | 'error'), 这样我可以在不改后端的情况下逐个预览。 先告诉我这四种状态分别打算怎么呈现,我确认后你再写代码。
That last line is critical. Asking it to describe the approach first lets you tell in seconds whether the empty state is yet another four-character "no data yet" — far faster than reading the code and redoing it.
Related Patterns
Real Examples
还没有任何笔记
What you write shows up here, ready to search and organize at any time.
新建第一篇Notion's empty database offers a few template options and a creation entry — using that space to teach the user what to do next.
Linear's loading skeleton reproduces the real content's row height and columns, so when the data arrives the switch is almost imperceptible and the page doesn't jump.
