On this page
Loading states
Choose what to show while something loads, and keep the page usable while it arrives.
Overview
A loading state tells someone the system is working. Choosing the right one can set an expectation of how long that will take. Used well a loading indicator can keep the user oriented, but overuse can make the product feel slower than it is.
The guidance below is organised by what is loading, rather than by how long the loading takes. The duration is still important, and should guide how much to show, but the thing being loaded should decide what to show.
The core decision
The screen should react to the click immediately, even when the thing being loaded is slow to arrive. Under about 100ms feels instant. Past that, show something straight away rather than waiting for the data.
The most appropriate pattern depends on two things: whether you know the shape of what's being loaded, and whether the wait is long enough to notice.
- If the shape of the result is known, draw it with skeleton components. A skeleton that matches the layout tells the user where the data will appear and lets them aim at it before it arrives.
- If the shape is not known, show a loading indicator, for example a spinner, instead. A chart or a result list whose size depends on the data has no layout you can draw up front.
- If the wait is short enough not to register, show nothing. A loader that flashes on and off will distract more than no loader at all.
Page and section loads
Use skeletons where the layout is predictable: Skeleton, SkeletonText, SkeletonHeading, SkeletonCircle and SkeletonCTA. See Skeleton Loading.
- Match the real layout closely. A skeleton that doesn't match what arrives will make the page jump, wasting the time it was meant to save.
- Skeleton the content, not the container. A card or table renders as normal, and the text, headings and images inside it get skeletons.
- Use SkeletonCTA for a button whose label or availability depends on data that is still loading.
- Show anything that doesn't depend on the data straight away, rather than waiting for the data to arrive. A page title that is always 'Payments' should render immediately, not as a grey bar.
Skeletons work best for waits long enough to notice but short enough to sit through, roughly a second to a few seconds. Beyond that, say something about progress instead of implying the content is nearly there.
Edit live code
<Card> <Stack spaceBetween={3}> <SkeletonHeading /> <SkeletonText lines={3} /> <SkeletonCTA size="12rem" /> </Stack> </Card>
When you cannot draw the layout
A spinner is for a region you cannot draw in advance, for example: a chart whose axes depend on the data, or a preview of an unknown file type. Most unknown cases are not really unknown, though. A paginated table knows its page size, so skeleton rows will match what arrives. A list with no bound does not, and guessing a row count causes the jump skeletons are meant to prevent.
- Several regions loading at once. Use skeletons where the regions can be drawn, or one spinner for the whole area where they cannot. Several spinners will read as several unrelated problems.
- Do not mix the two in the same view. Half grey blocks and half spinning circles will look broken rather than loading.
Nebula does not currently ship a spinner, so the ones in KAE come from components-core and semantic-ui.
Data refresh
If someone is looking at data, let them stay in control of whether it gets refreshed. The best way to handle a refresh depends on what triggered it.
If the user has initiated the refresh, they are waiting for the new data, so bring it in and keep as much of the page in place as you can.
- Replace only the part whose data changed. In a table, swap the rows for skeleton rows and leave the header and filters standing. Resetting the whole page loses the user's place and their scroll position.
- Put the loading state on whatever triggered it, like the filter or the refresh button.
- Do not dim or grey out data that is still readable. It takes away something useful and gives nothing back.
The data itself swaps when the new data arrives. If the refresh is quick enough not to register, show nothing at all and let it change.
If the system has newer data to show, do not swap it in while they are reading. Put a Notification below the nearest heading to say newer data is available, with an action to reload that region, and let them choose when. Notifications stay until they are acted on, so nothing is missed.
This matters most where someone is reading a record aloud to a customer. Content that changes mid-sentence will cause more trouble than content that's briefly out of date.
Actions in flight
When the user submits something, the feedback belongs on the thing they acted on.
- Use the loading prop on CTA Button. It replaces the button content with a loading indicator and disables the button while the action is in flight, which is the one place Nebula sanctions a disabled control. See Disabled states.
- Do not cover the page for an action that affects one part of it.
- Say what is happening, not just that something is. 'Saving' then 'Saved' tells the user more than a spinner.
Edit live code
() => { // ignore - for live code example only. const [saving, setSaving] = useState(false); return ( <PrimaryCTAButton loading={saving} onClick={() => { setSaving(true); setTimeout(() => setSaving(false), 2000); }} > Save </PrimaryCTAButton> ); }
Long waits
Where an operation runs long enough that the user could reasonably do something else, stop describing it as loading and start describing the progress.
- Use Progress Bar when real progress can be measured. A bar that visibly advances is the feedback, so do not put a spinner next to it as well.
- Where it cannot, set an expectation in words and let the user decide whether to wait.
- Beyond around ten seconds people abandon the task, so an unexplained wait of that length needs either progress or a way out.
Accessibility
- Announce that loading started, through an ARIA live region, for anyone who cannot see the skeleton or indicator.
- Announce that it finished. This is the step most often missed. Someone who cannot see an indicator disappear has no way of knowing the page is usable again, so convey completion through the live region or by moving focus to the content that arrived.
- Give every loading indicator a label that names the process: loading, saving, submitting.
- Do not stop the animation under reduced motion. A frozen indicator will look like a stalled process.
- Announce a failure too. A load that stops without explanation will leave a screen reader user with nothing to go on.
- Skeletons are decorative and should be hidden from assistive technology, with the live region carrying the message instead.
Copy guidance
Say what's loading
Not just that something is. Naming the thing tells the user whether they're waiting on the part they actually care about.
Do
Loading payments
Don't
Loading
Please wait
Carry the verb through every state
For an action, the label should always say what is happening or what just happened.
Do
Save → Saving → Saved
Don't
Save → Loading → Save
Use ellipses sparingly
An ellipsis is only needed where the label doesn't already read as in progress. A verb in its -ing form, alone or with a noun, is clear without one: 'Saving' and 'Loading payments' both work.
Where you do add one, use the single character (…) rather than three full stops.