# Spinner

> A spinning loading indicator. Zero JS.

URL: https://apogee.avaui.dev/docs/spinner
Last updated: 2026-10-15

A spinning loading indicator. Zero JS.

Last updated October 15, 2026

```astro
<Spinner label="Loading" />
```

## Add

```sh
npx @ava-ui/apogee add spinner
```

Ships `@lucide/astro` (`LoaderCircle`). No Apogee component dependencies.

## Usage

```astro
---
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:

```astro
<Spinner size="small" label="Loading" />
<Spinner label="Loading" />
<Spinner size="large" label="Loading" />
```

## Colours

```astro
<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:

```astro
<div class="text-primary"><Spinner label="Loading" /></div>
<div class="text-critical"><Spinner label="Loading" /></div>
```

## Examples

Standalone (announced) vs. decorative (parent already labelled):

```astro
<!-- Standalone: announced to screen readers -->
<Spinner label="Loading results" />

<!-- Inside an already-labelled context, e.g. a loading Button -->
<Spinner />
```

## Accessibility

-   With `label` set: renders `role="status"` + `aria-label={label}` — announced to assistive tech. It announces on mount, not on later updates (no `aria-live`); if the spinner appears without a full page navigation, consider wrapping it in your own `aria-live` region.
-   Without `label`: renders `aria-hidden="true"` — purely decorative, for use inside a parent that already communicates loading (e.g. `Button`’s `loading` prop sets `aria-busy` on the button itself).
-   The spin animation (`animate-spin`) is paired with `motion-reduce:[animation-duration:1.6s]` — it slows rather than stops under `prefers-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 `label` whenever 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 `label` on a Spinner that’s already inside a labelled loading control (e.g. `Button loading`) — that double-announces “loading.”
-   **Don’t** use `Spinner` for a placeholder-shaped loading state (a card, a list row) — use `Skeleton`.

## Content

`label` is a short present-participle phrase (“Loading”, “Saving changes”, “Loading results”) — not a full sentence, no ending punctuation.

## Related

[

LoaderTextText with a moving shimmer while something loads, crossfading to a done label when it finishes.

](https://apogee.avaui.dev/docs/loader-text)[

SkeletonA pulsing placeholder shown in place of content while it loads. Zero JS.

](https://apogee.avaui.dev/docs/skeleton)[

ProgressA determinate or indeterminate progress bar.

](https://apogee.avaui.dev/docs/progress)
