Empty State vs Skeleton
These are not alternatives, they are consecutive. A skeleton says “this is coming”; an empty state says “there is nothing, and here is why”. Showing the empty state while the request is still in flight tells the user something false, and they act on it — they leave, or they create a duplicate of the thing that was already there. The reverse is milder but still wrong: a skeleton that never resolves looks like a hang. The rule is simply not to guess before the answer arrives.
While you are still waiting, show a skeleton. Only once the answer is in — and it is zero — show an empty state.
Empty State
What a container shows when it holds no items, explaining why it is empty and what to do next.
Reach for it when
- First use, before the user has created anything. This one is really onboarding wearing a different hat — it is the best chance you get to explain the feature.
- A search returned nothing. Repeat what was searched for, because people mistype and cannot see their own query once it scrolls away.
- Filters excluded everything. Say which filters are active and offer to clear them — the way out matters more than the message.
- The user cleared the list themselves. This one can be cheerful; they did it on purpose.
Avoid it when
- The data is still loading. That is a skeleton — showing “No items” before the request finishes is a lie that costs trust.
- The request failed. An error is not emptiness; saying “No results” hides a problem the user could retry.
- You would only write “No data”. A blank screen with two dead words is worse than nothing, because it looks broken rather than empty.
- The container is one of many small ones on a dashboard. A full illustration inside a widget is noise; a single quiet line is enough.
Skeleton
A low-contrast shape that occupies the same space as the content still loading, so the layout is visible before the data arrives.
Reach for it when
- The layout is known in advance — a list of cards, a table, a profile header.
- Loading usually takes somewhere between 300ms and a couple of seconds.
- Several pieces of content arrive independently and you want the page to settle progressively rather than all at once.
- You want to avoid layout shift: the skeleton reserves the exact space the content will take.
Avoid it when
- Loads that finish under ~300ms. The skeleton flashes and reads as a glitch — render nothing instead.
- You don't know the shape of what's coming. A skeleton that doesn't match the final layout causes the shift it was meant to prevent.
- Waiting on an action the user just triggered, like submitting a form. A button spinner keeps the feedback attached to the thing they clicked.
- Long or open-ended waits. After a few seconds a skeleton stops reassuring and starts looking broken — switch to a progress indicator with an explanation.