Spinner
Indeterminate progress indicator. Two motion vocabularies ship: a rotating
ring (default) painted with one solid arc on a low-alpha track, and a
pulsing disc (.is-grow) for placements where a rotating ring
would compete with surrounding text. Three sizes (small / default / large)
and the shared .is-* combinations carry through.
Live
Rotating ring at three sizes
<div style="display: flex; gap: 1rem; align-items: center;">
<span class="spinner is-small" role="status" aria-label="Loading"></span>
<span class="spinner" role="status" aria-label="Loading"></span>
<span class="spinner is-large" role="status" aria-label="Loading"></span>
</div> @use '@adnap/krysalicss' with (
$feature-list: ('base-reset', 'base-global', 'element-spinner')
); Pulsing disc (.is-grow)
<div style="display: flex; gap: 1rem; align-items: center;">
<span class="spinner is-grow is-small" role="status" aria-label="Loading"></span>
<span class="spinner is-grow" role="status" aria-label="Loading"></span>
<span class="spinner is-grow is-large" role="status" aria-label="Loading"></span>
</div> @use '@adnap/krysalicss' with (
$feature-list: ('base-reset', 'base-global', 'element-spinner')
); Combinations (.is-primary / .is-warning / .is-danger / .is-success)
<div style="display: flex; gap: 1rem; align-items: center; flex-wrap: wrap;">
<span class="spinner is-primary" role="status" aria-label="Loading"></span>
<span class="spinner is-warning" role="status" aria-label="Loading"></span>
<span class="spinner is-danger" role="status" aria-label="Loading"></span>
<span class="spinner is-success" role="status" aria-label="Loading"></span>
<span class="spinner is-grow is-primary" role="status" aria-label="Loading"></span>
<span class="spinner is-grow is-warning" role="status" aria-label="Loading"></span>
<span class="spinner is-grow is-danger" role="status" aria-label="Loading"></span>
<span class="spinner is-grow is-success" role="status" aria-label="Loading"></span>
</div> @use '@adnap/krysalicss' with (
$feature-list: ('base-reset', 'base-global', 'element-spinner')
); Markup
<!-- Default rotating ring. -->
<span class="spinner" role="status" aria-label="Loading"></span>
<!-- Pulsing disc variant. -->
<span class="spinner is-grow" role="status" aria-label="Loading"></span>
<!-- Brand-tinted, large. -->
<span class="spinner is-primary is-large" role="status" aria-label="Loading"></span> Modes
- Rotating ring (default). A circular border with a low-alpha track and
one solid arc on the
block-startedge, rotated continuously. Reads as the canonical browser "loading" affordance. .is-grow. Swaps the ring for a filled disc that scales from 0 to 1 while fading out. Same accessibility semantics; useful when the spinner overlaps text or sits inside a tight slot where ring strokes feel noisy.
Sizes
.is-small—1remsquare.- (default) —
1.5remsquare. .is-large—2.5remsquare.
The border thickness is expressed in em so the ring scales proportionally
with the surrounding font-size when the spinner is dropped inside a button
or heading.
Combinations
The four .is-* combinations (is-primary, is-warning, is-danger,
is-success) tint the arc to the combination's background colour — the
arc IS the visible surface here, so the brand-signature surface bg carries
the right semantic (same rationale as Progress). The
default spinner paints in the default combination foreground.
Accessibility
- Always pair with
role="status"and anaria-label("Loading…", "Saving…", etc.) so screen readers announce the state. The visual is purely decorative; the announcement does the actual conveying. - Under
prefers-reduced-motion: reducethe rotation slows to 2s ($reduced-motion-duration) and the.is-growpulse is replaced with a static half-opacity disc. A frozen spinner reads as "broken"; a slow rotation still signals "in progress" without triggering vestibular discomfort (WCAG 2.3.3). - Under
forced-colors: activethecolor-mixtrack collapses; the plugin pins the track toCanvasTextand the arc toHighlightso the disc shape stays defined (WCAG 1.4.11).
Variables
| Variable | Default | Notes |
|---|---|---|
$selector | '.spinner' | Re-scope freely. |
$grow-selector | '.is-grow' | Modifier swapping ring for pulsing disc. |
$small-selector | '.is-small' | Small-size modifier. |
$large-selector | '.is-large' | Large-size modifier. |
$size | 1.5rem | Default square size. |
$small-size | 1rem | Small-size square. |
$large-size | 2.5rem | Large-size square. |
$border-width | 0.25em | Ring thickness. em-relative so it tracks the parent font-size. |
$duration | 0.75s | Rotation cycle. Linear (not eased) so the motion feels mechanical. |
$grow-duration | 1s | Pulse cycle for .is-grow. |
$track-mix | 25% | Static-track tint (currentColor over transparent). Mixing against transparent (not bg) keeps the ring overlay-safe on coloured surfaces. |
$default-combination | $color-combination-default | Fallback tuple when no --kc-spinner-fg is supplied. |
$reduced-motion-duration | 2s | Slowed rotation under prefers-reduced-motion: reduce. |
Override example
@use '@adnap/krysalicss/element/spinner' with (
$size: 2rem,
$border-width: 0.2em,
$duration: 1s,
$track-mix: 18%,
); Tokens consumed
| Token | Used for |
|---|---|
--kc-spinner-fg | Default arc colour (and disc fill under .is-grow). |
--kc-{label}-bg | Per-combination arc colour. The arc paints in the combination's bg — the visible surface carries the brand-signature colour. |