A wider screen, please

This page features interactive demos and side-by-side code samples that need at least 340 pixels of horizontal space. Please widen your browser window, or rotate your device, to continue.

Morpheus v0.3.0
v0.3.0

Spinner#

Component <neo-spinner> is a circular loading indicator with 2 modes. Indeterminate rotates a partial ring during work of unknown duration; determinate draws an arc filled to (value - min) / (max - min), the round counterpart of <neo-progress>. Sized off --neo-spinner-size (defaults to 1em) and inherits currentColor. The ring lives in an open shadow root; customize its ring, track, arc, and sun parts with ::part().

Default
Indeterminate
Determinate (progress ring)
Labelled, with value tooltip
Sizes
Inherits parent color
DeactivateRenameDuplicate
Delete
New state Toggle autoplay
HTMLCSS

The selected state's markup and its scoped CSS. The CSS is wrapped in <style>@scope { ... }</style> and applies to the preview only. Edits change the preview above, applied live while Sync is on, or when you click Patch or Replace. Patch morphs the preview; Replace swaps it outright.

ReplacePatch

Signals

Datastar signals declared in the selected state's markup. Change a value to update the preview, applied live while Sync is on, or when you click Patch.

Patch

Reference#

Attributes #

NameTypeDefaultDescription
valuenumber reflected observednoneWhen present (and without indeterminate), the ring is determinate. Clamped to [min, max].
minnumber observed0Lower bound of the determinate range.
maxnumber observed100Upper bound of the determinate range.
indeterminateboolean observedfalseForce the spinning mode even when a value is set. A spinner with neither value nor this attribute is indeterminate by default.
labelstring observednoneAccessible name. When set (or in determinate mode) the host reports role="progressbar" with the matching aria-value*; a bare unlabelled indeterminate spinner stays decorative.
easingstring observednoneAnimate the arc between value commits. Same shorthand as <neo-progress>'s easing. A bare duration (200ms) pairs with --neo-easing; a full value is used verbatim. Suppressed under reduced motion.

Parts #

NameDescription
::part(ring)The SVG element holding the track and arc.
::part(track)Faint full-circle backing ring.
::part(arc)The currentColor stroke that spins or fills to the value.
::part(sun)8-ray starburst shown under reduced motion. Pulses its opacity in indeterminate mode.

CSS variables #

NameDefaultDescription
--neo-spinner-size1emOuter diameter.
--neo-spinner-duration0.8sRotation period in indeterminate mode.
--neo-spinner-ring-width3Stroke width in the 24-unit viewBox.
--neo-spinner-track-colorcolor-mix(in srgb, currentColor 20%, transparent)Backing-ring color.
--neo-spinner-arc-transition0s linearTransition applied to the arc between value commits in determinate mode.

Examples#

Indeterminate#

A bare <neo-spinner> with no value stays decorative and CSS-driven; pair it with adjacent status text.

Loading…

Determinate (progress ring)#

A faint track with a currentColor arc filled by value. No text is drawn on the ring; the value goes to aria-valuenow, and a wrapping <neo-tooltip> can surface it visually (see below).

25% 60% 100%

Live progression#

The ring is just an attribute; set value from anything (SSE, a fetch progress callback, a timer) and the arc sweeps to it. easing tweens the arc between commits (same syntax as <neo-progress>); this one is wired to a 60 ms ticker.

Labelled, with value tooltip#

With a label the host reports role="progressbar" with aria-valuemin / aria-valuemax / aria-valuenow, which makes it a real, announced progress indicator, not just a decorative glyph. Tooltip wraps it in a <neo-tooltip> so the value (with Unit) appears on hover or focus, never painted on the ring. Color follows currentColor.

Sizes#

Set the visual size with the --neo-spinner-size custom property. It defaults to 1em so a bare spinner inherits the surrounding line-height naturally; override it inline (or via a wrapper class) to get any other size without touching the markup.

Inherits parent color#

The spinner's stroke is currentColor. Wrapping it in any element with a color recolours both modes. Theme tokens like var(--accent) work too. The demo below shows an indeterminate spin and 3 determinate rings at 25%, 66%, 100%, each in a different parent colour.

Under prefers-reduced-motion: reduce, the indeterminate spinner swaps the rotating arc for an 8-ray sun starburst (the canonical lucide loader glyph) that slowly pulses its opacity. That keeps it visually distinct from a determinate ring sitting at ~75%, which is what the frozen 3/4 arc would look like otherwise, and shows the work is still running without rotation. An opacity change is not motion animation according to WCAG 2.3.3. The determinate ring keeps its fill and only drops its commit tween. Values jump rather than sweep. The kit's --neo-spinner-duration token sits on its own (not routed through the global --neo-duration-scale) so the spin isn't accidentally stopped when other kit motion is dialed down.

Examples: Datastar#

Morphing during interaction#

A Datastar app can fat-morph the page at any moment, switching between unknown progress and server-reported percentages. Each state re-emits only the bare host. The SVG ring lives in the component's shadow root. The morph leaves the existing arc in place. The easing transition sweeps between determinate values, while the indeterminate state switches the same arc back to its rotating animation. Autoplay cycles through all 5 states below with no signals or morph hints.

Indetermined
0%
30%
70%
100%
DeactivateRenameDuplicate
Delete
New state Toggle autoplay
HTMLCSS

The selected state's markup and its scoped CSS. The CSS is wrapped in <style>@scope { ... }</style> and applies to the preview only. Edits change the preview above, applied live while Sync is on, or when you click Patch or Replace. Patch morphs the preview; Replace swaps it outright.

ReplacePatch

Signals

Datastar signals declared in the selected state's markup. Change a value to update the preview, applied live while Sync is on, or when you click Patch.

Patch

Settings

Theme

DefaultSunshineOceanMatrixRose☀ ◐ ☾

Accessibility

Playgrounds

Server Simulator