# Every list has four states

> Design the loading, empty, error and full branches of any list or page, and get an agent to build all four.

Module: Components and states · Level: beginner · 7 min · https://school.agents.org.in/design/components/four-states

Watch a list arrive. First its outline, then a problem, then a retry, then the rows.

*[Interactive demo: A skeleton in the rows' own shape, an error that offers a way out, then the content.]*

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.

*[Interactive demo: Same list, same box. The designed version keeps its height in every state.]*

*(A missing branch in the code is a finding, even if the screen looks done.)*

## 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 skeleton (A gray placeholder in the exact shape of the content that is loading: circles where avatars go, bars as wide as the words. When the real content arrives, nothing moves) 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 layout shift (When something on the page jumps because content above or around it changed size, like a list collapsing to a spinner and then expanding when the data lands), and it is the first thing that makes a screen feel cheap.

> **In plain words.** **Skeleton**: a gray outline of the content shown while it loads. **Spinner**: the rotating circle. Spinners are fine inside a button; skeletons are for areas.

## Empty: three jobs

An empty state (What a list or page shows when there is no data yet. A good one says what belongs there and offers the action that fills it, instead of just "No data") is often the very first thing a new user sees. "No data" wastes it. A good one does three things:

1. **Says what belongs here.** "No projects yet."
2. **Points to the next action.** A real button: "Create project".
3. **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 (avoid):**

**No data**

  **Designed (do this):**

**No projects yet**<br />A project keeps one client's files, notes and invoices together.<br />`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.

**All four states (paste into your agent):**

```text
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.
```

**Check yourself:** A list takes about 2 seconds to load. Which loading state fits?

- [ ] A centred spinner in the middle of the empty page
- [ ] Nothing at all until the data arrives
- [x] A skeleton with the shape of the loaded rows, shown after a ~150ms delay
- [ ] A progress bar with a percentage and a cancel button

*Why:* Content areas get a skeleton by 400ms, delayed ~150ms so fast loads never flash it. A centred spinner collapses the page; progress bars are for waits over 3 seconds.

## In the skill

- https://school.agents.org.in/skill/components/MOC-components.md
- https://school.agents.org.in/skill/components/empty-loading-states.md
