Skip to content

Spinner

hc-spinner is a pure-CSS spinning ring for “busy” / pending states. It has no JavaScript: it animates with CSS, follows currentColor by default, and pairs with htmx’s .htmx-indicator class for request-driven visibility.

The spinner inherits its indicator color from the surrounding text (currentColor), so it sits naturally inside buttons, badges, and prose.

Set data-size="sm" or data-size="lg"; omit it for the default medium.

By default the indicator uses currentColor. data-variant swaps it to a semantic accent — handy when the spinner is not inside colored text.

VariantIndicator color
omittedcurrentColor (inherited).
primaryAccent / primary action.
successSuccess green.
warningWarning amber.
errorError red.

Add the htmx-indicator class so htmx fades the spinner in only while a request it drives is in flight — no JS of your own. The button stays the network owner.

<button class="hc-button" data-hx-post="/save" data-hx-disabled-elt="this">
Save
<span class="hc-spinner htmx-indicator" aria-hidden="true"></span>
</button>

Here the spinner is decorative (aria-hidden="true") because the disabled button and the swapped response already communicate progress. When the spinner is the only signal, give it a status name instead — see below.

A spinner is a visual; the “busy” meaning must reach assistive tech as text. Two patterns:

  1. Name the spinner itself — make it a live region with an accessible name:

    <span class="hc-spinner" role="status" aria-label="Loading"></span>
  2. Wrap it with a visually-hidden label — keep the spinner decorative and put the text in a sibling. Use this when you want richer wording:

    <span role="status">
    <span class="hc-spinner" aria-hidden="true"></span>
    <span class="hc-sr-only">Loading results…</span>
    </span>
  • Reduced motion: under prefers-reduced-motion: reduce the spin slows but does not stop — it must keep signalling that work is in progress. The accessible name keeps announcing it regardless of motion, so the status is never conveyed by animation alone.
  • Don’t leave an empty role="status" with no name; an unnamed live region announces nothing.
Token pathPurpose
spinner.sizeDefault (medium) diameter.
spinner.sm-size / lg-sizedata-size diameters.
spinner.border-widthRing thickness.
spinner.track-colorThe faint full ring (theme border).
spinner.indicator-colorThe leading arc (default currentColor).
spinner.durationOne rotation duration.
spinner.reduced-durationRotation under reduced motion.
spinner.{primary,success,warning,error}-colordata-variant indicator colors.
Show the generated CSS variables
--hc-spinner-size | -sm-size | -lg-size
--hc-spinner-border-width | -track-color | -indicator-color
--hc-spinner-duration | -reduced-duration
--hc-spinner-primary-color | -success-color | -warning-color | -error-color
  • Progress — for determinate progress with a known percentage.
  • Skeleton — placeholder shapes for content that is loading.
  • Button — common host for an inline htmx-indicator spinner.