Spinner

A hand-drawn indeterminate loading indicator that respects reduced motion.

Overview

A Spinner is an indeterminate busy indicator — a small hand-drawn ring that rotates while work of unknown duration is in progress. Use it when you can’t show meaningful progress; use Progress when you can.

Anatomy

A sketchy ellipse ring (@ghds/sketch-core) rendered in the primary colour and rotated. Because the outline is hand-drawn and irregular, the rotation reads clearly without needing a gap or gradient. Three sizes — sm, md (default), lg — map to comp.spinner.size.*; the spin duration is comp.spinner.duration.

Variants & States

State Status Notes
Spinning Implemented The only visual state — the ring rotates continuously.
Reduced motion Implemented Rotation is suppressed under prefers-reduced-motion; the ring is shown static.
Hover / Focus / Disabled Not implemented Spinner is not interactive.

Usage

Do

  • Give a meaningful label describing what is loading (e.g. "Loading results").
  • Use for short, indeterminate waits; switch to Progress when you can report a percentage.
  • Place the spinner where the incoming content will appear so focus/attention stays put.

Don't

  • Don't use a spinner for waits long enough to warrant progress or a skeleton screen.
  • Don't rely on motion alone — the accessible label conveys the busy state to assistive tech.
  • Don't stack many spinners; one indicator per loading region is enough.

Accessibility

Content

No visible text — the label is for assistive tech only. Keep it short and specific.

Props API

React@ghds/react

PropTypeDefaultDescription
size'sm' | 'md' | 'lg''md'Rendered diameter.
labelstring'Loading'Accessible label for the busy state.
...restHTMLAttributes<HTMLSpanElement>Native <span> attributes pass through.

Web Components@ghds/web-components

PropTypeDefaultDescription
size'sm' | 'md' | 'lg''md'Rendered diameter (reflected attribute).
labelstring'Loading'Accessible label for the busy state.

React Native@ghds/react-native

PropTypeDefaultDescription
size'sm' | 'md' | 'lg''md'Rendered diameter.
labelstring'Loading'Accessible label for the busy state.
testIDstringTest handle for queries.

Code examples

// React — @ghds/react
import { Spinner } from '@ghds/react/spinner';

<Spinner size="lg" label="Loading results" />;
<!-- Web Components — @ghds/web-components -->
<script type="module">
  import '@ghds/web-components/spinner';
</script>
<gh-spinner size="lg" label="Loading results"></gh-spinner>
// React Native — @ghds/react-native
import { Spinner } from '@ghds/react-native/spinner';

<Spinner size="lg" label="Loading results" />;

Live Demo

React @ghds/react

Web Components @ghds/web-components