Skip to content

Interface

Loading, empty and error, the three states every demo skips

Every screen has four states and a demo only ever shows one of them. The other three are where trust is won or lost, and they are almost always the last thing anybody builds.

The short answer

Reserve the space before content arrives so nothing jumps. Distinguish empty because nothing exists from empty because a filter excluded everything, and never render a failure as an empty set. Every error says what failed, whether anything was saved, and what to do next.

Four states, one of which gets built

Loading, empty, error, filled. The demo is always filled. The design review is always filled. The first day of real use is mostly the other three.

They are not edge cases. Loading happens on every single view. Empty happens to every new user on every screen, at precisely the moment they are deciding whether this product is any good. Error happens on the worst possible day.

Loading: reserve the space

The goal is not to entertain somebody during the wait. It is to stop the page from moving.

Where the shape of the result is known, use a skeleton with the same dimensions as the content that will replace it. The layout is then already correct when the data arrives and nothing shifts. Where the shape is genuinely unknown, a spinner is honest, but the container should still hold a minimum height so the page does not collapse and then expand.

Two details matter more than the choice of indicator. Do not show a loading state at all for something that resolves in under about a quarter of a second, because a flash of skeleton is worse than a brief pause. And on a slow request, say something after a few seconds, because silence past that point reads as broken.

Empty: which empty is this

Three different situations get one generic message far too often, and they need different answers.

Nothing exists yet. This is the new user’s first impression and it is the highest leverage empty state in the entire product. It should explain what the screen is for and offer one obvious way to create the first thing.

A filter excluded everything. The answer is which filter, and a control to clear it. Showing “nothing here yet” to somebody who has a hundred records and one active filter is actively misleading.

View only, and there is nothing. Somebody without permission to create cannot act on a call to action. Tell them where the thing comes from instead, so they know who to ask.

Error: never let it look empty

The most damaging pattern in this whole area. A request fails, the component renders whatever it has, which is nothing, and the user sees an empty list.

They believe it. They conclude the data is gone. They raise a ticket, or they re enter something that already exists, which creates a duplicate somebody now has to clean up. A user interface decision has become a data problem.

Failure must look like failure, visibly distinct from empty, always.

A useful error message contains four things: what failed in plain words, whether anything was saved, what to do next, and an identifier support can search for. Nobody needs a stack trace and nobody is helped by “something went wrong” on its own.

The cheapest quality signal in software

None of this is difficult. It is a couple of hours per screen, and it is the whole difference between a product that feels solid and one that feels like a prototype somebody shipped.

The reason it gets skipped is that it never appears in a demo, so nobody ever asks for it. Which means building it is a decision the engineer makes alone, unprompted, which is exactly why it is a useful thing to know about somebody.

Questions people actually ask

Skeletons or spinners?
Skeletons where the shape of the result is known, because they reserve the layout and stop the page jumping. Spinners where it is not. A spinner replaced by content of a different height causes exactly the shift that skeletons exist to prevent.
Why is a failure rendered as empty so damaging?
Because the user believes it. They conclude their data is gone and act on that belief, which usually means a support ticket or, worse, re-entering something that already exists and creating a duplicate.
What belongs in an error message?
What failed in plain words, whether anything was saved, what to do next, and an identifier support can search for. Not a stack trace, and not an apology on its own.

Published July 2026, updated July 2026.

Hiring someone to own work like this?

I am open to senior backend and full stack roles, remote and permanent.

Get in touch / Read the CV