Skip to main content

Loading Status

Indicates that content or a process is in a loading or pending state. Use spinners for small inline states and skeleton variants for larger content areas. Don't use a spinner for a process with a known completion percentage.

Figma Storybook Docs

Description

What it does

Communicates to users that the system is working — either loading content, processing a submission, or waiting for a response — so they know to wait rather than repeat an action.

Where it appears

Inside buttons during form submissions, inline next to data that is fetching, overlaid on panels when content is loading, and as skeleton placeholders replacing lists or cards before data arrives.

Why it exists

Government users often work on slower networks or with large datasets. Without clear loading feedback, users re-submit forms or assume a process has failed — causing duplicate records or lost trust in the system.

Dependencies

Button, Progress Bar

Anatomy

PartRequired?Notes
Spinner track Required The full circular path the spinner animates along. Uses --op-color-border-default as the base track colour.
Spinner arc Required The animated portion that rotates around the track. Uses --op-color-interactive-default (green-400) for the active arc, or inherits the button's text colour when inside a button.
Label (spinner) Optional Short visible text beside the spinner, e.g. 'Loading...' or 'Saving...'. Omit when space is constrained; always provide an accessible label via aria-label regardless.
Skeleton block Required A grey rounded rectangle that stands in for a loading content region. Width and height should approximate the real content it replaces.
Skeleton shimmer Recommended A left-to-right animated gradient sweep across the skeleton block. Provides motion cue that the state is transient, not an error. Must be suppressed under prefers-reduced-motion.
Skeleton text lines Optional Multiple stacked skeleton blocks at text-line height, used to approximate a paragraph or list while it loads. Last line should be shorter (approx 60%) to mimic natural text wrap.

Variants

Spinner — default

Inline indeterminate loading state

Small components: inside buttons, next to a field being validated, or beside a single data value being fetched. Use when the loading area is smaller than approximately 120px.

Spinner — overlay

Full-panel or full-section loading state

When an entire card, table, or modal content area is loading and the underlying content is not yet available. Pair with aria-busy on the container.

Skeleton — card

Placeholder for a card-shaped content block

Replacing stakeholder profile cards, consultation summary cards, or engagement activity cards while data loads. Matches the card's approximate dimensions.

Skeleton — list

Placeholder for a list of rows

Replacing tables or lists of stakeholders, submissions, or notifications while the dataset is fetching.

Skeleton — text block

Placeholder for a paragraph or multi-line description

Replacing consultation descriptions, stakeholder notes, or rich text fields while content is loading.

States

State Behaviour
Loading Spinner rotates continuously or skeleton shimmer animates. The surrounding container has aria-busy='true'. Interactive elements within the loading region are disabled.
Complete Loading indicator is removed and real content appears. aria-busy is removed or set to 'false'. No additional announcement is needed unless the content change is not visually apparent.
Error (post-load) Loading indicator is replaced by an error state. Use the system's inline error or empty state pattern. Do not leave a spinner running if loading has failed.
Reduced motion Spinner rotation and skeleton shimmer animation are suppressed via prefers-reduced-motion: reduce. The spinner track and arc remain visible as a static indicator; skeleton blocks remain visible without the sweep.

Usage guidelines

When to use

  • A user triggers an action (form submission, search, navigation) that requires a network request or server-side processing before content can be displayed.
  • Content inside a panel, table, or card is being fetched asynchronously on page load.
  • A button action is in flight — e.g. submitting a stakeholder record or publishing a consultation.
  • Multiple content areas are loading simultaneously — use skeleton variants to preserve layout and reduce perceived load time.

When not to use

  • When completion percentage is known — use Progress Bar instead.
  • For instantaneous actions (under ~300ms) — avoid flashing a spinner for state changes the user will not perceive.
  • As a permanent empty state — if content genuinely does not exist, use an empty state component, not a skeleton.
  • To indicate success or failure after a process completes — use a Toast, Alert, or inline validation message instead.

Do / Don't

Do

Replace skeleton placeholders with real content as soon as the data is available, even if other regions are still loading.

Don't

Hold all content until every request resolves — this maximises perceived wait time unnecessarily.

Do

Add a visible 'Loading stakeholders...' label or aria-label to spinners so screen reader users receive an equivalent experience.

Don't

Use a spinner with no accessible label — screen readers will announce nothing meaningful.

Do

Disable submit buttons and set aria-disabled while a form submission is in flight to prevent duplicate submissions.

Don't

Leave form controls active during loading — government users may submit duplicate consultation responses or stakeholder records.

Do

Size skeleton blocks to closely approximate the content they replace to minimise layout shift when content arrives.

Don't

Use a single full-width skeleton bar for every content type — mismatched skeletons increase perceived jank.

Was this page helpful?

Updated 9 September 2026