Spinner
Loading indicator
Intent
Spinner is a pure-CSS rotating ring for loading states.
Inline-block, sized in em by default so it
tracks the surrounding font size, with explicit size variants
for larger chrome. Use it for indeterminate waits -- fetching
data, waiting for a response -- where there's nothing concrete
to progress against.
Basic usage
A bare .spinner with role="status"
and an aria-label announces itself as a loading
state to assistive tech. The ring's visible arc paints in
currentColor, so it inherits the surrounding
text color unless a color variant says otherwise.
<span class="spinner" role="status" aria-label="Loading"></span>Variants
Size
Sizes step through the density grammar: .is-sm
(1em, slightly smaller than default for dense chrome), the
default (1.25em, matches typical inline use),
.is-lg (2rem, fixed-size for card or panel
waits), .is-xl (3rem, for full-page or hero
loading states). The default and .is-sm track
the surrounding font-size; the two larger sizes are absolute
so they don't grow with large ambient text.
<div class="cluster is-snug">
<span class="spinner is-sm" role="status" aria-label="Loading"></span>
<span class="spinner" role="status" aria-label="Loading"></span>
<span class="spinner is-lg" role="status" aria-label="Loading"></span>
<span class="spinner is-xl" role="status" aria-label="Loading"></span>
</div>Color
The default spinner paints in currentColor so it
inherits the surrounding text color -- useful when the spinner
sits inside colored or themed chrome. .is-primary
and .is-accent remap the arc color explicitly.
The track (the faint ring behind the arc) is always the
theme's shadow tone, not the variant color, so the arc reads
as distinct from the ring.
<div class="cluster">
<span class="spinner is-lg" role="status" aria-label="Loading"></span>
<span class="spinner is-lg is-primary" role="status" aria-label="Loading"></span>
<span class="spinner is-lg is-accent" role="status" aria-label="Loading"></span>
</div>Inline with text
The default spinner is sized in em, so it scales
with the element it sits inside -- a spinner inside
<small> renders smaller than one inside a
heading. Useful for inline loading phrases like
"Saving…" where the spinner should match the text
rhythm rather than impose its own scale.
Fetching your data
Loading dashboard
Saving draft<div class="stack is-relaxed">
<p class="cluster">Fetching your data <span class="spinner" aria-hidden="true"></span></p>
<h3 class="cluster">Loading dashboard <span class="spinner" aria-hidden="true"></span></h3>
<small class="cluster">Saving draft <span class="spinner" aria-hidden="true"></span></small>
</div>Class reference
| Class | Role |
|---|---|
.spinner |
Inline-block rotating ring. Default size is 1.25em; default color is currentColor. |
.is-sm, .is-lg, .is-xl |
Size variants. .is-sm tracks font-size; .is-lg and .is-xl are absolute. |
.is-primary, .is-accent |
Color variants. Remap the arc color to the matching semantic family; track stays neutral. |
Customization
Under prefers-reduced-motion: reduce the
rotation slows to 1.5s rather than stopping,
because users still need to know a wait is in progress.
Full token list and defaults:
src/css/spinner.css.
Accessibility
-
Announce the loading state. A visual
spinner alone isn't announced by screen readers. Wrap
with
role="status"and anaria-label(or adjacent live-region text) so assistive tech announces "Loading" when the spinner appears.role="status"is a polite live region -- it waits for a pause in speech before announcing, which is the right tone for background waits. -
Use
aria-busyon the container that's loading. If the spinner sits inside a card that will eventually hold content, setaria-busy="true"on the card. Screen readers announce the busy state and suppress stale content; toggle tofalsewhen loading completes. -
Decorative spinners get
aria-hidden. When the surrounding copy already names the state ("Fetching your data…"), mark the spinneraria-hidden="true". The text is the announcement; the spinner is just the visual cue. -
Button loading uses
aria-busy, not.spinner. A button carryingaria-busy="true"renders its own spinner (shared keyframe) and gets the right semantics for free. Don't put.spinnerinside a button's label.
CSS Reference: Spinner
CSS-only loading indicator
Source: src/css/spinner.css
Tokens
| Token | Default | Description |
|---|---|---|
--spinner-size | 1.25em | Diameter of the spinner |
--spinner-width | 2px | Border thickness of the spinner ring |
--spinner-color | currentColor | Color of the spinning arc |
--spinner-track | var(--color-shadow) | Color of the background track |
--spinner-speed | 0.6s | Duration of one full rotation |
Classes
| Class | Description |
|---|---|
.spinner | component root |
.is-sm | Smaller spinner at 1em |
.is-lg | Larger spinner at 2rem with thicker ring |
.is-xl | Extra-large spinner at 3rem with thicker ring |
.is-primary | Primary color arc |
.is-accent | Accent color arc |