Spinner
A spinning loading indicator. Zero JS.
Last updated
Show codeHide code
<Spinner label="Loading" />Add
npx @ava-ui/apogee add spinner
Ships @lucide/astro (LoaderCircle). No Apogee component dependencies.
Usage
---
import Spinner from '@/components/apogee/Spinner/Spinner.astro'
---
<Spinner label="Loading" />
API
@composable — exposed to Content Editors in AVA. Any other SVG attribute
passes through (e.g. data-*).
| Prop | Type | Default | Description |
|---|---|---|---|
size |
'small' | 'medium' | 'large' |
'medium' |
Icon size (size-4 / size-5 / size-8). |
label |
string |
— | Accessible label. Set it for a standalone spinner (renders role="status" + aria-label); omit it when a parent already conveys loading (e.g. Button’s aria-busy) — it then renders aria-hidden. |
color |
'inherit' | 'primary' | 'neutral' | 'critical' | 'positive' | 'warning' |
'inherit' |
Hue; inherit takes the surrounding text colour. |
class |
string |
— | Extra classes, merged last. |
Sizes
Every size:
Show codeHide code
<Spinner size="small" label="Loading" />
<Spinner label="Loading" />
<Spinner size="large" label="Loading" />Colours
Show codeHide code
<Spinner color="inherit" label="inherit" />
<Spinner color="primary" label="primary" />
<Spinner color="neutral" label="neutral" />
<Spinner color="critical" label="critical" />
<Spinner color="positive" label="positive" />
<Spinner color="warning" label="warning" />States
Always animating (animate-spin) — no hover/focus/disabled states; it’s not
interactive. Inherits text-current, so it takes the colour of its container:
Show codeHide code
<div class="text-primary"><Spinner label="Loading" /></div>
<div class="text-critical"><Spinner label="Loading" /></div>Examples
Standalone (announced) vs. decorative (parent already labelled):
<!-- Standalone: announced to screen readers -->
<Spinner label="Loading results" />
<!-- Inside an already-labelled context, e.g. a loading Button -->
<Spinner />
Accessibility
- With
labelset: rendersrole="status"+aria-label={label}— announced to assistive tech. It announces on mount, not on later updates (noaria-live); if the spinner appears without a full page navigation, consider wrapping it in your ownaria-liveregion. - Without
label: rendersaria-hidden="true"— purely decorative, for use inside a parent that already communicates loading (e.g.Button’sloadingprop setsaria-busyon the button itself). - The spin animation (
animate-spin) is paired withmotion-reduce:[animation-duration:1.6s]— it slows rather than stops underprefers-reduced-motion, since the spin itself is the loading indicator. - No JS: this component ships zero JavaScript.
Tokens
Spinner reads no Apogee tokens directly — it inherits colour via
currentColor. Set a text-colour utility (text-primary, text-critical, …) on
a wrapping element, or pass class, to theme it.
Do / Don’t
- Do set
labelwhenever the spinner stands alone (a full-page loader, a panel’s loading state) — it’s the only thing announcing “loading” to screen reader users. - Do let it inherit colour rather than hard-coding one, so it matches its surrounding context.
- Don’t set
labelon a Spinner that’s already inside a labelled loading control (e.g.Button loading) — that double-announces “loading.” - Don’t use
Spinnerfor a placeholder-shaped loading state (a card, a list row) — useSkeleton.
Content
label is a short present-participle phrase (“Loading”, “Saving changes”,
“Loading results”) — not a full sentence, no ending punctuation.