Design HubStudio

12. Empty, error & loading states

The three states between "nothing here yet" and "everything's working." Most products treat them as afterthoughts. Done well, they're some of the most considerate parts of a UI — and the easiest to recognize when they're missing.

Gallery

Three canonical patterns. All follow the same shape: icon (or skeleton) + headline + supporting copy + action.

No devices yet

Add your first device to get started.

!
Couldn't load devices

Connection to the workcell controller failed.

When to use each

Three decisions: whether the empty state earns design, which loading affordance fits, and how much an error should explain.

Empty state — design or skip
No workflows yet

Create your first workflow to start running methods.

Design — when the user can act
The user is meant to populate this surface and hasn't yet. The empty state explains what belongs here and points to the action that fills it. Headline names the missing thing; the CTA names the verb.
All clear

No active alerts. The workcell is operating normally.

Skip the CTA — when empty is good news
Some emptiness is a feature: zero alerts, zero errors, an inbox cleared. There's no "action" the user should take. Skip the CTA; the icon and copy do the work. Don't manufacture an action just to have a button.
Loading — skeleton, spinner, or nothing
Skeleton — when layout is known
Use for content loads >300ms where the final shape is predictable (lists, tables, cards). Skeleton bars hint at what's coming, so the layout doesn't shift when data arrives. The skeleton must match the layout it replaces.
Working…
Spinner — when duration is unknown
Use for indeterminate operations affecting a specific control or short async actions where layout doesn't apply (button submits, inline saves). Persistent spinners read as "broken"; if the operation takes >5s, use a progress bar or status text instead.
Sometimes neither — operations under ~300ms should render their result instantly. A spinner that flashes for 100ms reads as flicker, not feedback. Trust the speed; show nothing.
Error — what to say
!
Something went wrong

An error occurred. Please try again.

Generic — useless
"Something went wrong" tells the user nothing they didn't already know. They see broken UI; the message confirms it. No mention of what failed, why, or whether retrying will help.
!
Couldn't reach the workcell controller

Last seen 4 minutes ago. The controller may be offline or unreachable on the network.

Specific — actionable
Names the failed thing, suggests the likely cause, and offers two paths: retry (optimistic) and view status (diagnostic). The user knows what broke and what to try.
Drift to avoid

Six named anti-patterns. Empty-and-error drift is especially common because these states ship last — after the happy path is "done."

  • Don't ship a feature without an empty state
    Principle 5
    The empty state is the user's first impression of any feature. A blank table with no copy, no icon, no CTA reads as broken UI — not as "you haven't done this yet." Cited in Tables §8c; canonical here.
  • Don't write "Something went wrong"
    UX
    The single most-common error message in software, and the least useful. Always name the failed operation, suggest the likely cause, and surface at least one path forward. "Couldn't load methods — check your connection" is ten-times the user-respect for the same character budget.
  • Don't show a spinner for fast operations
    UX
    A spinner that appears and disappears in under 300ms reads as flicker — visual noise without information. For sub-300ms operations, render the result. The user's brain interprets "instant" as faster than "fast spinner."
  • Don't use a skeleton that doesn't match the final layout
    UX
    Skeleton bars exist to hint at the shape of what's coming. A four-bar skeleton replaced by a one-line summary causes jarring layout shift; the skeleton was a lie. Match the skeleton structure to the real content's structure.
  • Don't pair an empty state with a CTA that leads nowhere
    UX
    "Get started" buttons that route to a marketing page, a setup wizard that's been removed, or a generic dashboard all break the contract. If you can't ship the destination, don't ship the button. Drop the CTA; let the headline do the work.
  • Don't hide the empty state below the fold
    UX
    An empty state buried under page chrome (long header, navigation, summary bar) gets missed. Place the empty state where the data would have been — the user's eye is already there.
Composition — same view, three states

The same "Method library" panel in all three states. Considering states as siblings of the populated state — not as exceptions — produces better feature design. Build the empty, error, and loading variants alongside the happy path, not after it.

Loading
Method library
Empty
Method library
No methods yet

Create your first method to start running protocols on the workcell.

Error
Method library
!
Couldn't load methods

The library service didn't respond. Last successful sync 12 minutes ago.

  • same shell All three states use the same .card + .card-header. The body changes; the container doesn't
  • specific copy Each state names the thing — "methods", not "data". "Library service didn't respond", not "Something went wrong"
  • one CTA per state Empty: create. Error: retry. Loading: none — skeleton is the affordance
  • design states as siblings Treat empty/error/loading as part of the feature, not exceptions to it. The user spends time in all three.