Progress
LoadingA bar that fills to its value when it scrolls into view, with the number counting up beside it, or a ring with the number in the middle.
An indeterminate arc whose length breathes while it rotates, so it looks like it is working rather than spinning a fixed wedge. One SVG circle, pure CSS; announces itself as a status.
Live, from the library itself: scroll, hover, navigate away and back. The demo is never gated.
Adjust · props are custom properties, set inline, so you edit the running instance
JavaScript of its own
0 B
CSS 4.8 kB raw including the base tokens. Measured from a production build, and again in CI for every change that can move it.
Browser support
every browser.
One command adds the integration, the base tokens and every component. Then import what you use.
npx astro add moonarcpnpm astro add moonarcbunx astro add moonarc---
import Spinner from '@moonarc/core/Spinner';
---
<button disabled><Spinner size={16} /> Saving…</button>Owns the file, no dependency. The registry item also installs the base tokens.
npx shadcn@latest add https://moonarc.dev/r/spinner.jsonThe CLI needs a components.json and the @/* alias, which Setup has. The file lands in src/components/moonarc/.
The whole component. Self-contained styles in a cascade layer so your classes always win; it imports nothing.
---
/**
* Spinner — an arc that chases its tail, zero JS. One SVG circle: the dash
* length breathes while the whole thing rotates, so the arc grows and
* shrinks like a real indeterminate indicator, not a fixed wedge.
*/
import type { HTMLAttributes } from 'astro/types';
interface Props extends HTMLAttributes<'span'> {
/** Diameter in px. */
size?: number;
/** Stroke width in px. */
width?: number;
/** Colour. */
color?: string;
/** Accessible label. */
label?: string;
/** Seconds per rotation. */
duration?: number;
}
const { size = 20, width = 2, color = 'currentColor', label = 'Loading', duration = 1.2, class: className, ...rest } = Astro.props;
---
<span class:list={['ma-spinner', className]} role="status" aria-label={label} style={`--ma-spin-size:${size}px;--ma-spin-w:${width}px;--ma-spin-color:${color};--ma-spin-dur:${duration}s`} {...rest}>
<svg viewBox="0 0 24 24" aria-hidden="true">
<circle cx="12" cy="12" r="10" fill="none" stroke="currentColor" stroke-linecap="round" />
</svg>
</span>
<style is:global>
@layer components {
:where(.ma-spinner) {
display: inline-block;
width: var(--ma-spin-size, 20px);
height: var(--ma-spin-size, 20px);
color: var(--ma-spin-color, currentColor);
vertical-align: middle;
}
:where(.ma-spinner svg) {
display: block;
width: 100%;
height: 100%;
animation: ma-spinner-rotate var(--ma-spin-dur, 1.2s) linear infinite;
}
:where(.ma-spinner circle) {
stroke-width: var(--ma-spin-w, 2px);
stroke-dasharray: 63;
stroke-dashoffset: 47;
animation: ma-spinner-dash calc(var(--ma-spin-dur, 1.2s) * 1.5) var(--ma-ease-in-out) infinite;
}
@media (prefers-reduced-motion: reduce) {
:where(.ma-spinner circle) {
animation: none;
stroke-dashoffset: 47;
}
:where(.ma-spinner svg) {
animation-duration: calc(var(--ma-spin-dur, 1.2s) * 2.5);
}
}
}
@keyframes ma-spinner-rotate {
to {
rotate: 360deg;
}
}
@keyframes ma-spinner-dash {
0% {
stroke-dashoffset: 56;
}
50% {
stroke-dashoffset: 16;
}
100% {
stroke-dashoffset: 56;
}
}
</style>| Prop | Type | Default | Description |
|---|---|---|---|
size | number | 20 | Diameter in px. |
width | number | 2 | Stroke width in px. |
color | string | 'currentColor' | Colour. |
label | string | 'Loading' | Accessible label. |
duration | number | 1.2 | Seconds per rotation. |
A fixed arc rotating at less than half speed, still readable as loading but no longer busy.
ClientRouterCSS-only; nothing to rebind.
Replaces: Spinner (shadcn) · react-spinners. See the migration table.
A bar that fills to its value when it scrolls into view, with the number counting up beside it, or a ring with the number in the middle.
Rings expand from a point and fade, one after another, like a pulse.
Placeholder lines, optionally with an avatar, shimmering while content loads.
Also: view transitions · how costs are measured · browser support · accessibility policy · five-minute setup · MCP for agents · this page as Markdown