A spinner says one thing: something is happening. It has no progress, no estimate and no
words, which is why it is small, and why it is the right answer only for a wait short
enough that the reader does not need to know more than "still working".

It is **decorative**, and the component states that in its markup: the SVG carries
`aria-hidden="true"`. So a spinner never announces anything itself. Inside a control it is
described by the control; on its own it needs a wrapper that carries the status and the
words. A skeleton says the shape of what is coming; a `loading-state` claims the region;
a spinner is the busy mark that fits in a button, a row or beside a line of text.

```tsx demo="feedback/spinner-preview"
```

## Usage

On its own, the spinner is only half the answer, the wrapper is the other half:

```tsx
import { Spinner } from "@asheeui/web";

<div role="status">
  <Spinner />
  <span className="sr-only">Loading the invoice lines</span>
</div>
```

Inside a control, the control does the talking:

```tsx
import { Button } from "@asheeui/web";

<Button isLoading>Saving invoice</Button>
```

## Inside a control

A spinner in a button is named by the button. There is nothing for the ring to announce:
the reader pressed a control, so the control is what has to report that it is working, and
the ring is the last few pixels of that report. `Button`'s `isLoading` does it, the
label stays in the accessible name, `aria-busy` states that it is busy, and a second press
is refused while the first is in flight.

```tsx demo="feedback/spinner-inside-control"
```

The consequence is a rule: never put a spinner in a control that has no other label. An
icon-only button still needs its `aria-label`, and a spinner beside a line of text needs the
text to be the thing that is announced, the ring adds nothing to either.

## Sizes and colour

`size` follows the density scale the fields and buttons use, so a ring and the label beside
it agree. `color` is a role rather than a hex value, and it should be the role of the work
it reports about: `danger` inside a destructive action, `success` while a sync finishes,
the application's accent otherwise. `speed` is a CSS duration, so a genuinely slow import
can turn more slowly than the rest of the page.

The rotation is applied under `motion-safe`, so a reader who asked their system for reduced motion
gets a still ring rather than a spinning one, the movement is the embellishment and the label is
the report, which is why the ring is never the only thing saying that something is happening.

```tsx demo="feedback/spinner-sizes"
```

## Props

```props Spinner
```

`size` is registered as `md` and `speed` as `0.75s`. The speed is the framework's own shared
value, so a configuration file that states it changes the ring on both platforms rather than
on the web alone. `color` is deliberately **not** registered: it resolves from the
application's `defaultColor`, so a product's accent reaches every spinner without being
restated. `className` is appended last, which is where a consumer's own colour or margin
wins. The component forwards the rest of its props, and its ref, to the `<svg>`.

## Accessibility

- The element is `aria-hidden="true"`, so it has no role, no name and no announcement.
  Whatever the wait means has to be said by something else on the page.
- Inside a control, that something is the control: `Button` with `isLoading` keeps the
  action's label in the accessible name, prefixed by a visually hidden `Loading`, and sets
  `aria-busy` so the state is announced rather than only drawn.
- On its own, wrap it in `role="status"` and give the wrapper words, a visually hidden line
  beside the ring, or `aria-label`, because a region with nothing in it announces nothing.
- A spinner reserves only its own footprint. Size the region it stands in close to the
  content that will replace it, so the page does not jump when the wait ends.

The two channels, and why a spinner is deliberately silent while a loading region is not,
are described in [Announcements](/concepts/announcements).

## Related

```related skeleton, loading-state, button
```
