Skip to content
FrastoUI

Spinner

Status: Experimental

Spinner communicates a short indeterminate wait through nine decorative square cells. Six presentation variants share the same loading semantics, fixed dimensions, color inheritance, and zero-JavaScript rendering.

The default orbit variant moves a leading square around the perimeter with a fading trail while the center remains quiet.

Refreshing customers

Pair a decorative Spinner with visible status text.

---
import { Spinner } from '@freight-px/frasto';
---
<span style="display:inline-flex; align-items:center; gap:.5rem;">
<Spinner />
Loading invoices
</span>

If visible text is not appropriate, provide label so the root becomes one polite status announcement.

<Spinner label="Loading invoices" />
Visible loading text Accessible label only

Variants change presentation only. They never change loading semantics or announcement behavior.

Orbit Breathe Cascade Counter Typewriter Refresh
  • orbit — a clockwise perimeter lead with two trail intensities.
  • breathe — a center-out pulse through edges and corners.
  • cascade — a diagonal intensity wave from top-left to bottom-right.
  • counter — two opposing perimeter leads with short trails.
  • typewriter — row-by-row reveal, hold, and gradual reset.
  • refresh — two clockwise perimeter heads kept half a loop apart.
Small Medium Large
Size Dimension Typical use
sm 12px compact buttons and table tools
md 16px inline status and default controls
lg 20px standalone local progress

Cells and gaps scale inside the fixed root dimension, so animation never moves surrounding content.

Spinner uses currentColor and does not infer a semantic tone. Apply color to its parent when the surrounding operation has a meaningful status.

Syncing Slower cycle
<span style="color:var(--frasto-info);">
<Spinner variant="cascade" /> Syncing
</span>
<Spinner
variant="typewriter"
style="--frasto-spinner-duration:2400ms;"
/>

--frasto-spinner-duration is an advanced presentation override. Avoid very fast cycles that make state difficult to follow.

Button uses the default Orbit Spinner while preserving its label width, preventing duplicate activation, and exposing aria-busy="true" on the control.

Prop Type Default Purpose
variant 'orbit' | 'breathe' | 'cascade' | 'counter' | 'typewriter' | 'refresh' 'orbit' chooses the square-matrix motion
size 'sm' | 'md' | 'lg' 'md' fixes the indicator dimensions
label string — exposes the Spinner as an accessible status
class string — adds a consumer class

Native span attributes are forwarded except role, aria-label, and aria-hidden, which the component owns. Use exported SpinnerVariant and SpinnerSize types when a wrapper exposes these choices.

  • All nine cells are decorative and hidden from assistive technology.
  • Without label, the Spinner root is aria-hidden="true"; pair it with visible status text or an already-labelled busy control.
  • With label, the root uses role="status" and the supplied accessible name.
  • Prefer one status announcement for the affected region instead of labelling several indicators.
  • For a longer wait, keep explanatory text and page context visible.

All six animations stop under prefers-reduced-motion. A calm static matrix with varied opacity remains visible while surrounding text or the root label continues to communicate progress.

Use Spinner for a short local operation whose final structure is already present. Use Skeleton when initially unavailable structured content needs to reserve its eventual layout. Use a future determinate Progress component when a reliable completion value is available.