Loading state
Introduction
Loading states matter almost just as much as the loaded state. Every time data is fetched, an action is triggered, or a page is navigated to, the user sees something, and that something should feel intentional. The goal is a stable, predictable UI with no layout jumps or sudden visual changes.
As a designer, think about loading states for every view and component that displays data. For every screen you design, ask yourself: what does this look like before the data arrives?
| Scenario | Pattern |
|---|---|
| Navigating to a page or loading section data | Skeleton |
| A button triggering a slow action | Button with spinner |
| File upload or known-progress operation | ProgressBar |
Skeleton loading
When a user navigates to a new page, render skeletons for the content area. The skeletons should mirror the actual layout of the page so that content snaps in without any layout shift when it arrives.
Only skeleton the content that is actually being fetched. Things like page titles, navigation, and labels that are hardcoded into the design should show up immediately. Putting skeletons on static content just adds noise and hides information the user could already see.
Cards, tables, and sections that fetch their own data should show skeletons within their own bounds, without blocking the rest of the page. Match the number of skeletons to the number of items you expect, or at least 3-4 rows/items.
Action feedback
When a user clicks a button that triggers a slow action (saving, submitting, sending), set the button to its inactive state and show a spinner. This tells the user something is happening without disrupting the rest of the interface, and keeps the button accessible to screen readers — unlike a disabled button, which is hidden from assistive technology entirely.
Only add a loading indicator when the action takes more than 2-3 seconds.
When the action finishes, show a FloatingMessage to confirm the result. The
button returning to its normal state is often too subtle on its own, and users
may not notice whether the action succeeded or failed. Use state="success" on
completion and state="alert" if something went wrong.
See the Button documentation for full guidance on button loading states, including icon-only buttons with spinner tooltips and custom inactive labels.
File uploads and known-progress operations
When progress is measurable, like during a file upload or a multi-step background job, use a ProgressBar with a descriptive label. This tells the user both that something is happening and how far along it is. Keep the rest of the UI functional while progress is underway.
See the ProgressBar documentation for guidance on labels and state colors. Also see the File input area documentation for file upload best practices.
Best practices
- Always design a loading state alongside the loaded state. For every screen that displays data, think about what it should look like before the data arrives.
- Keep skeletons faithful to the layout. A skeleton that looks nothing like the real content will still cause a layout jump when the content loads. Match the shape, size, and quantity of the actual content.
- Don't mix patterns in the same context. Avoid showing a skeleton for some content and a spinner for adjacent content at the same time.
- Avoid full-page spinners for navigation and data loading. They block the interface and cause a layout shift when content arrives. Reserve them for bulk operations where blocking interaction is intentional.