The states you forgot to design
Every piece of loaded data has four states. Most designs draw one.
A mockup shows the happy path: a list with six nice items in it. Reality has three more states, and users hit all of them.
- Loading: the request is in flight. What is on screen right now?
- Empty: it worked, there is genuinely nothing. A blank area reads as broken.
- Error: it failed. Can the user do anything about it, or just feel bad?
- Success: the one that got designed.
Loading states have a subtlety worth knowing. A spinner that appears for 80 milliseconds and vanishes reads as a flicker and feels worse than nothing. A spinner that appears after a short delay, and once shown stays for a minimum time, feels calm. That is why routers expose exactly those two knobs.
What does a brand new user with no data see here?
The empty state is the first thing every single user experiences, and it is the state most likely to have never been looked at.
What to remember
- Loading, empty, error, success: design all four.
- Empty and error must be visibly different.
- Delay loading indicators slightly, then hold them briefly.
Terms in this lesson
Field notes
Loaded from a deliberately slow source. The lesson above was already readable while this was still travelling. That is streaming, and it is the same trick a chat interface uses.
The bug was three caches deep
A team spent two days on a "deploy that did not work". The deploy was fine. A CDN rule was caching the HTML document itself for an hour, so every visitor kept getting the previous build. The fix was one header; finding it needed knowing the copies existed.
Reported by a solo founder, 2024
Nobody had opened the empty state
A product launched with a dashboard that looked superb with demo data and completely blank on day one. Every new signup saw an empty white rectangle and assumed the app was broken. Signup-to-activation doubled after a two-hour fix.
Common enough to be a cliché
resolved in 901ms · region iad1
Hide field notes toggles a search param the loader reads. With it off, the slow promise is never created, so nothing streams.