Book a callGitHub147

Craft05/05

Empty, loading and error states

Updated · Suggest a change

Most screens spend part of their life without their data. They're empty before the first item, loading while it arrives, or broken when a request fails. These in-between states are where people get stuck, so each one needs a design of its own.

The component library has 22 finished parts for them, in its States aisle. Like every library part, they use only your design tokens, so they take your colours, type and spacing.

Skeleton or spinner? Use a skeleton when a whole area is loading, so the page keeps its shape. Use a small spinner only inside the button or field the person just used, and only for waits of a few seconds.

The checklist

Every screen that shows data needs a plan for each of these:

  • First use: nothing has been made yet
  • No results: a search found nothing
  • Filtered to nothing: filters hide every item
  • All done: the person cleared their queue
  • Loading: a skeleton for an area, a spinner for a control, progress for a long task
  • A failed save: put the change back and say why (optimistic update)
  • A field that can't be accepted: a message under the field
  • Not found (404), something went wrong (500), offline, maintenance, no access (403) and session expired
  • Done: a toast for small changes, a success screen for ones people want proof of

Every state answers three questions

  1. What happened? In plain words, without blaming the person. "We can't find that page", not "Invalid URL".
  2. Is my work safe? Say so when it is: "Your text is kept on this page".
  3. What can I do now? One clear next step: search, try again, ask for access or clear the filters.

A joke, a code and no way on

It doesn't say what failed, whether the work is lost or what to do next. The code means nothing to the reader.

What happened, what's safe, what to do

Plain words, no blame, the person's work kept, and one clear action. The reference code is for support, and says so.

Two error messages for the same failed save. The first jokes, shows a raw code and offers nothing to do. The second says what failed, that the work is kept and what to do next, and labels the code as something to give support.

The words follow the house copy rules: sentence case, no "Oops" and no joke when something failed.

Pages for when the whole screen can't show

PageWhenIt offers
Page not found (404)The link is old or the page movedA search, Go to home and Go back
Something went wrong (500)The server failedTry again, and a reference code for support
OfflineNo connectionWhat still works on this device, and what waits
MaintenancePlanned downtimeWhat's paused, that nothing is lost, and when it's back
No access (403)Signed in, but not allowedWho owns it, and a way to ask for access
Session expiredSigned out mid-taskSign in again, with the draft kept

Empty is not one state

A list can be empty for four different reasons, and each needs different words.

Why it's emptyPartSays
Nothing has been made yetFirst useWhat this area is for, and the one action that starts it
A search found nothingNo resultsWhat was searched and where, and ways to widen it
Everything is doneAll doneThat nothing is waiting, and when new items will show up
Filters hide everythingFiltered to nothingWhich filters are on, and a button to clear them

Loading: match the wait

under 1 sShow nothing newThe button says “Saving…” and the old view stays. A spinner that flashes for a moment reads as a glitch.
1 to 10 sA spinner, or a skeletonA small spinner in the control the person pressed. A whole area that's loading gets a skeleton of its own shape.
over 10 sProgress with stepsA bar with a percentage, the time left, each step done, now and next, and a way to keep working.
Three ways to show a wait, picked by how long it takes. Under a second, nothing new appears; from 1 to 10 seconds, a spinner in the control or a skeleton of the area; over 10 seconds, a progress bar with steps and the time left.

The inline spinner waits about a second before it shows, so a quick answer never flashes. A system made with OpenDesigner can set these limits with its --ds-state-wait-* tokens; the parts fall back to 1 second where a system doesn't have them.

Skeletons that don't jump

A spinner in an empty box

The box is short while it loads. When the rows arrive, everything under it jumps down, often just as someone goes to click.

76 px while loading244 px once loaded: the page below moves 168 px

A skeleton of the final shape

Grey bars stand where the text will be, at the same line height. The data fills them in and nothing moves.

the same height loading and loaded0 px of movement
The same list loading two ways. A spinner in an empty box makes the page jump by the height of the rows when they arrive. A skeleton holds each row's place at its final size, so the loaded content takes over without moving anything.

The five skeleton parts (text, card, list, table and media) are the loaded layout itself, greyed out. Each line keeps its font and line height and stays on one line, and each picture keeps its frame. Each was measured in a browser at 1440 and 375 px wide, and none of the five moved by a single pixel when its data arrived. While your data loads, fill the part with placeholder text of a typical length. Each line is held to one line, so the real text can be longer or shorter without changing the height.

While loading, the area is marked busy for screen readers and can't be tabbed into. A short status says when it starts and when it's done. With reduced motion turned on, the grey bars stay still instead of shimmering.

Short waits and long tasks

  • Inline spinner: inside the button or beside the field that's working. The button stays focused and can't be pressed twice.
  • Progress with steps: for imports, exports and uploads. It announces each new step, not every percent.
  • Optimistic update: shows a small change at once. If the save fails, it puts things back and says why, with a retry.

Feedback after an action

PartUse it for
Page bannersNews about the whole page or account: info, success, warning and error. The error banner stays until it's fixed
Toast with undoA reversible action such as archive. Undo puts the item back where it was, and the toast has no timer
Inline field errorOne field that can't be accepted. The message sits under the field and clears once the value is right
Success confirmationThe end of a task people want proof of: what happened, the details to keep, and what happens next
For developers

Each part's meta.json lists when to use it and when not, its keyboard behaviour and how it handles seven edge cases: empty, loading, error, disabled, overflow, long text and a missing image. A fixture.json beside it holds its sample data. python3 skills/opendesigner/scripts/library.py list --type states lists all 22. Where a system has the --ds-state-* tokens, the parts use them; every use falls back to a token every system has, and library.py check enforces that.