Every list has four states
beginner·7 min·Markdown for agents
What this lesson teaches
Design the loading, empty, error and full branches of any list or page, and get an agent to build all four.
Watch a list arrive. First its outline, then a problem, then a retry, then the rows.
That took about five seconds and showed you three different screens. Most generated UI only ever designs the last one, because the test account always has data. The states are the work. Components are where people touch your product, and their states are where most interfaces get lazy.
Four branches, every time
Every list, table, feed and dashboard has four states:
| State | When you see it | Its job |
|---|---|---|
| Loading | The data is on its way | Hold the shape so nothing jumps |
| Empty | There is no data yet | Explain what belongs here and how to add it |
| Error | The data could not be fetched | Say what happened and offer a real fix |
| Full | The data is here | The screen you actually designed |
Flip through them below, then switch to Agent default to see what you get when nobody asks.
Says what belongs here, why it matters, and the one action that fills it. Same height as the full list.
Loading: delay, then a skeleton
Speed is a feeling, so the indicator depends on how long the wait will be.
| Expected wait | Show |
|---|---|
| Under about 150ms | Nothing. Delay every indicator ~150ms so fast answers never flash one |
| By 400ms | A for a content area; a spinner only inside the button that started the work |
| 3 to 15 seconds | A progress bar with a percentage |
| Over 15 seconds | Progress, time left, and a cancel button |
A skeleton is a gray placeholder in the exact shape of the content: circles where avatars go, bars as wide as the words. Size each row from the real row (48px because the loaded row is 48px, not a guess). When the content arrives, nothing moves.
A centred spinner as the loading state of a whole list is a defect: the page collapses to a dot, then everything jumps when the data lands. That jump is called , and it is the first thing that makes a screen feel cheap.
Empty: three jobs
An is often the very first thing a new user sees. "No data" wastes it. A good one does three things:
- Says what belongs here. "No projects yet."
- Points to the next action. A real button: "Create project".
- Optionally, adds context. One line on why it matters, or an example.
Size it like the full state, so the first item does not shove the layout. An empty search result says what was searched and offers to clear it. And never auto-redirect from an empty state ("nothing here, taking you home..."): it feels like the app broke.
✕ Default
No data
✓ Designed
No projects yet
A project keeps one client's files, notes and invoices together.Create project
Error: what happened, and a way out
An error state answers two questions: what went wrong, and what can I do now. "Something went wrong." answers neither. "Couldn't load projects. The server didn't answer. Your projects are safe." plus a Try again button that really refetches answers both. The framework's built-in error page and 404 count as missing states, too.
One more state: the first paint
A theme that flashes light before going dark, a sidebar that renders open then snaps shut: both happen because a saved value was read after the page painted. The fix is to decide before the first paint. A tiny script in the page head sets the theme class; layout choices like a collapsed sidebar come from a cookie the server reads; anything else waits to render until its value is known.
Getting an agent to do it
Agents build the full state because that is what their test data shows. Ask for the other three by name, with the numbers.
For every list, table and feed in this screen, build all four states: loading, empty, error and full. - Loading: delay any indicator 150ms; then a skeleton with the loaded rows' exact height, widths and gaps. No centred spinner. - Empty: a title saying what belongs here, one line of why, and the primary action button. Same height as the full list. - Error: what happened in plain words, that nothing was lost (if true), and a Try again that refetches. - Persisted UI state (theme, sidebar) is set before first paint, not in an effect. List any view where a branch was missing before you started.
Quick check
A list takes about 2 seconds to load. Which loading state fits?
In the skill
The nodes your agent reads when it does this work.
- MOC — ComponentsThe atoms users actually touch — hover states, empty/loading states, icons.
components/MOC-components - Empty and loading statesEvery data view has four branches — loading, empty, error, full. Empty states direct action; errors offer a specific recovery. Delay any indicator ~150ms, show one by 400ms — a skeleton for content, a spinner only inside a control, progress bars past 3s. Persisted state is correct on first paint.
components/empty-loading-states
Connected lessons
What this lesson builds on and leads to, across every course.