Loading State
Concept
A screen telling the user content is still on its way, so waiting doesn't get mistaken for something being broken.
Why it exists
Screens used to just sit empty until the data arrived. Open a document list with nothing in it and a user reads it one of two ways: there are no documents, or the page is broken. Neither is true, and nothing on the screen distinguished the two.
Behavior split the same way. Whoever read it as empty went to create a new document; whoever read it as broken hit refresh or closed the tab. The list filling in a second later doesn't help the second group, who already left.
What was needed was a marker separating still coming from not there. That's the job the loading state holds, and it's why an empty list and a loading list should never look alike.
How it works
It turns on the moment a request goes out and off when the response arrives. Four things to decide.
- Pick the treatment: a spinner, a grey outline of the content about to arrive — skeleton UI — or nothing at all. A skeleton previews the shape that's coming, which suits lists and cards; a spinner suits places where the shape can't be predicted.
- Delay turning it on. Requests that answer inside 200 milliseconds get no indicator. A flash that appears and vanishes is more distracting than the wait it covered.
- Give more information as it runs long. Past one second, show that something is still happening; past ten, show how much is left or which step it's on.
- Always hand off to the next state. There are three destinations: the filled screen, an empty state when there's nothing, and an error state when it failed. A spinner that runs forever is usually the third handoff missing.
With a skeleton, match the height of the real content. If the skeleton is shorter, the page shifts down the instant data lands, and whoever was reaching for a button presses something else.
Some places drop this state entirely by using optimistic UI, which assumes success and updates before the server answers. Actions like pressing a like button qualify: failure is rare and reversing it is cheap.
How it sounds at work
"The spinner just keeps going. I don't think there's a handoff to the error state."
Nowhere was built for the request to go when it fails.
"At 150 milliseconds it's better to show nothing. The flash is more disorienting."
Delay when the indicator turns on.
"The skeleton is shorter than the real row, so the page jumps when content lands."
Heights before and after arrival have to match.
The Pagedeck document list uses a skeleton. Noa matched one skeleton row to the height of one real document row, and Theo implemented it to skip the skeleton for responses under 200 milliseconds. A failed search once left the skeleton running indefinitely, which was fixed by adding the handoff to the error state.
Where to go next
- UI state
see how the data-side states connect to each other
- Empty state
the screen that most often follows this one
- Web performance
the side that shortens the wait itself
Last updated 2026-09-16