Malevich

Primitives/

Spinner

Single rotating ring. 4 sizes, inverse/muted. Reduced-motion aware.

Spinner

Indeterminate loading indicator. CSS-only — a single rotating ring, no JavaScript. Used inline next to text, inside buttons during async work, or as a centered indicator inside an empty container.

When to use

For multi-step or skeleton-style loading, prefer Skeleton. For inline pulsing dots, prefer Dots.

Variants

Variant Class Use for
Default (m) .spinner Standard size, accent color
Small .spinner.-s Inline with body text, button icons
Large .spinner.-l Section-level loading
Extra large .spinner.-xl Centered page-level loading
Inverse .spinner.-inverse On dark / accent backgrounds
Muted .spinner.-muted De-emphasized loading

Modifiers compose: .spinner.-l.-inverse is allowed.

Anatomy

<span class="spinner" role="status" aria-label="Loading"></span>

<!-- Inside a button -->
<button class="button -primary" aria-busy="true">
  <span class="spinner -s -inverse" aria-hidden="true"></span>
  Submitting…
</button>

<!-- Centered -->
<div style="display:grid; place-items:center; min-height:200px;">
  <span class="spinner -xl" role="status" aria-label="Loading"></span>
</div>

Tokens used

From semantic tier

Component-tier (defined inline)

Accessibility

The spinner element carries role="status" and aria-label="Loading" (authored on the element). Screen readers announce the loading state when the element appears in the DOM.

Inside a button, the spinner replaces the icon during aria-busy="true" and should be marked aria-hidden="true"; the button's accessible name (text or aria-label) carries the meaning. The button's aria-busy attribute is the source of truth for assistive technology.

The component honors prefers-reduced-motion: reduce by slowing the rotation from 900ms to 2400ms. It does not stop the animation entirely because loading without visual feedback is worse for recognition than a slowed spinner.

Edge cases

Do

Don't