Skip to main content
Designing for the In-Between States
January 15, 2025
8 min read
DesignUX

Designing for the In-Between States

Empty, loading, partial, error, transition. The states nobody designs first but everyone hits first

You open Figma. You design the dashboard with beautiful data, the profile with a perfect avatar, the list with exactly the right number of items. That mockup feels crisp.

Then real life shows up. Your user just signed up, so their dashboard is blank. Their list is empty. The API timed out, and they're watching a spinner that never stops.

None of that is in your mockup. But it's what most people see most of the time. The empty screen is the first thing a new user meets. The slow load is the everyday experience on a train or a cheap phone. The error is the make-or-break moment. Design only the happy path, and you've designed the part of your product people see least.

Five states nobody designs first

  • Empty: no data yet. You just signed up and the screen is bare. Or you cleared everything and it's bare again.
  • Loading: data is coming, but not here yet. Could be 200ms. Could be ten painful seconds.
  • Partial: half the page made it. One API responded, another is still hanging, a third fell over.
  • Error: something cracked. Network failure, bad input, permission denied.
  • Transition: the gap between two known states. An item being deleted. A form being submitted. A page changing.

Each one needs its own design. Treating them all as "just show a spinner" is the fastest way to make your product feel brittle.

Empty states are your first impression

The empty state is the most important screen in your product. Every new user sees it before they see anything good. Not your polished dashboard. The blank one. And in most design files, it's a grey box that says "No items yet."

There are three empty states, and they are not the same:

  • First run: the user just arrived with nothing. This is onboarding in disguise. Show what goes here and how to add the first one.
  • No results: they searched or filtered and nothing matched. Don't say "No data." Echo what they looked for and offer a way out: clear the filter, broaden the search, fix the typo.
  • Cleared out: they emptied it on purpose. This one can celebrate. "Inbox zero" is an empty state people work toward.

Whatever the flavor, a good empty state does three things:

  1. Explains what belongs here: not "No data" but "Your projects will show up here once you create one."
  2. Gives one clear action: a button, a link, a nudge. Don't make someone guess how to fill the void.
  3. Feels intentional: an illustration, a warm line, anything that says "we thought about this" instead of "we forgot about this."

The gap between polished and half-finished is almost always the state nobody designed first.

Loading should say something, not just spin

Time changes what good looks like. Match the feedback to the length of the wait.

  • Under 200ms: show nothing. An indicator that flashes for a single frame feels more broken than no indicator at all. Hold the loading state behind a short delay.
  • 200ms to 2 seconds: skeleton screens win. They show the shape of what's coming without shouting about the wait, and your brain fills in the rest.
  • Over 2 seconds: acknowledge it. A progress bar, step-by-step feedback, or at the very least a spinner with words: "Loading your project files." A bare spinning circle never tells you whether to wait or walk away.

One pattern to avoid: a fully rendered page with empty boxes that pop with content a second later. That layout shift feels like the floor moving under your feet. Reserve the space before the data lands.

Partial states: when half the page shows up

Real pages don't load all at once. One call answers in 80ms, another takes three seconds, a third fails outright. Design for the in-between, not just all-or-nothing.

  • Render in chunks: paint each section the moment its data lands instead of blocking the whole page on the slowest call.
  • Show stale, then fresh: if you have last-known data cached, show it right away and refresh in the background. A slightly old number beats a spinner.
  • Contain the damage: if one widget can't load, let the rest work and show a small error in that one spot. Don't take the whole page down for a single failed call.
  • Treat offline as a state: cached content, a quiet "You're offline" marker, and a queue that retries actions once the connection comes back. Not a crash.

Errors are a design opportunity

Networks fail. Servers go down. People type things you didn't expect. Errors aren't the exception, so the real work is whether your error screen helps or shrugs.

Most error messages are written for the developer who wrote them. "Error 500: Internal Server Error" tells your user nothing. "Something went wrong" is barely better. It's a shrug with a stylesheet.

Good error design has three parts:

  • What happened, in plain language: "We couldn't save your changes."
  • Why, when you can say: "Your connection dropped."
  • What to do next, always: "Try again" or "Check your connection and retry."

Not all errors are the same error. A validationerror (you typed a bad email) belongs right next to the field, the moment it's wrong, in calm language. A system error (the server fell over) belongs at the top of the action, and should never blame the user. A permissionerror deserves its own care: say what they can't reach and who to ask, not a raw "403."

Then protect the work. If a form submission fails, keep the form filled. If a file upload breaks, keep the file selected. Losing your input on top of an error is the fastest way to lose you for good.

Transition states: the space between two states

You click delete. For a moment the item is neither there nor gone. That moment is a design decision, not a gap to ignore.

  • Go optimistic when you can: update the UI as if the action already worked, then reconcile when the server answers. It makes the app feel instant. Just keep a clean way to roll back if it fails.
  • Confirm without a wall: a toast, a checkmark, a row sliding out. Enough to say "done," not a modal that demands a click.
  • Disable, don't freeze: while a form submits, disable the button and show it working. Don't let someone submit twice because nothing on screen changed.
  • Animate to explain, not decorate: a short motion that shows where a thing went helps. A flourish on every click gets old by the third time.

Principles that hold up

  1. Design the empty state first. Make the blank screen inviting and the full one takes care of itself.
  2. Delay your spinners on purpose. A 150ms pause before the loading indicator kills the flash and, strangely, makes things feel faster.
  3. Never let the page jump.Hold space for content that's loading. Skeletons, fixed heights, anything that keeps the ground steady.
  4. Make every error recoverable.Every error state needs an action: "Try again," "Go back," "Contact support." Never a dead end.
  5. Keep the user's input sacred. A failure should never cost someone the work they already did.
  6. Throttle your own network to 3G. Use your product on a rough connection for a day. The in-between states you never noticed will be all you see.

Next time you open a design file, don't start with the ideal state. Start with the blank screen. The skeleton. The half-loaded page. The error.

Design those well, and your product feels considered even when things go wrong. Which, for most people most of the time, is exactly when it counts.