# Spinner

> Package `@umriss-ui/core`, version 0.40.0. Demo page: <https://romanhaendler.github.io/umriss-ui/core/spinner/>. Every page in one line: [llms.txt](https://romanhaendler.github.io/umriss-ui/core/llms.txt); every page in full: [llms-full.txt](https://romanhaendler.github.io/umriss-ui/core/llms-full.txt).

A turning circle that says something is running, without saying how far (also called a loading indicator). Reach for it for short waits whose length nobody knows, beside a word that says what runs.

```ts
import { Spinner } from "@umriss-ui/core";
```

Demo page: https://romanhaendler.github.io/umriss-ui/core/spinner/

It takes the colour of the text around it and announces itself as “Loading”; name it better with `aria-label` where the word beside it is not enough.

## Examples

### With a word beside it

A spinner says only that something runs; the words beside it say what.

```tsx
import { Spinner, Stack, Text } from "@umriss-ui/core";

export default function WithAWord() {
  return (
    <Stack direction="row" gap={2} align="center">
      <Spinner aria-label="Loading the tours" />
      <Text size="sm" tone="secondary">
        Loading the tours of North depot …
      </Text>
    </Stack>
  );
}
```

### Sizes

`size` is the edge length in pixels; match it to the text it stands beside – 12 for small print, 14 for body text.

```tsx
import { Spinner, Stack } from "@umriss-ui/core";

export default function Sizes() {
  return (
    <Stack direction="row" gap={4} align="center">
      <Spinner size={12} />
      <Spinner size={14} />
      <Spinner size={20} />
      <Spinner size={32} />
    </Stack>
  );
}
```

### Colour

It draws in `currentColor`, so it takes the colour of whatever it stands in and needs no tone of its own.

```tsx
import { Spinner, Stack, Text } from "@umriss-ui/core";

export default function Colour() {
  return (
    <Stack direction="row" gap={5} align="center" wrap>
      <Stack direction="row" gap={2} align="center">
        <Spinner size={14} />
        <Text size="sm">Refreshing</Text>
      </Stack>
      <Stack direction="row" gap={2} align="center" style={{ color: "var(--u-color-accent)" }}>
        <Spinner size={14} />
        <Text as="span" size="sm" style={{ color: "inherit" }}>
          Syncing the calendar
        </Text>
      </Stack>
    </Stack>
  );
}
```

### Refresh a panel in place

While a panel reloads, the button that started it carries the spinner - `loading` on a `Button` - and the old figures stay readable below it. The button stays, so the head keeps its height.

```tsx
import { useEffect, useState } from "react";
import { Button, Card, CardBody, CardHeader, Stack, Text } from "@umriss-ui/core";

export default function APanelRefreshing() {
  const [loading, setLoading] = useState(false);

  useEffect(() => {
    if (!loading) return;
    const timer = setTimeout(() => setLoading(false), 1500);
    return () => clearTimeout(timer);
  }, [loading]);

  return (
    <Card style={{ maxWidth: 420 }}>
      <CardHeader
        title="Open incidents"
        actions={
          <Button size="sm" variant="ghost" loading={loading} onClick={() => setLoading(true)}>
            Refresh
          </Button>
        }
      />
      <CardBody>
        <Stack gap={2}>
          <Text size="sm">INC-1048 · Checkout slow, card payments time out</Text>
          <Text size="sm">INC-1047 · Webhook deliveries delayed</Text>
        </Stack>
      </CardBody>
    </Card>
  );
}
```

## When to use something else

- You know how far the job has come → ProgressBar
- You know the shape of the content that is coming → Skeleton
- A button waiting for the action it started → `loading` on [Button](https://romanhaendler.github.io/umriss-ui/core/button/)

## Keyboard

The keys of [Button](https://romanhaendler.github.io/umriss-ui/core/button/#keyboard-button) apply here.

## Accessibility

An `svg` with the role `status`, named "Loading" by the wording or by your `aria-label`; the role stays whatever you pass. It draws no text, so its name is all a screen reader hears of it. Where a word beside it already says what runs, as in a loading toast, hide it with `aria-hidden`.

With reduced motion it turns at less than half its speed instead of stopping, so it still says that something runs.

## API

### `SpinnerProps`

| Prop | Type | Default | Description |
|---|---|---|---|
| `size` | `number` | `14` | Edge length in pixels.<br>Shown in: [Sizes](https://romanhaendler.github.io/umriss-ui/core/spinner/#sizes), [Colour](https://romanhaendler.github.io/umriss-ui/core/spinner/#colour) |
| `ref` | `Ref<SVGSVGElement>` | — | Receives the component's root element. From `RefProp`.<br>Shown in: [A row that holds still (Sizes)](https://romanhaendler.github.io/umriss-ui/core/sizes/#a-row-that-holds-still), [An anchored surface (Popover)](https://romanhaendler.github.io/umriss-ui/core/popover/#anchored-surface), [Take the width of the trigger (Popover)](https://romanhaendler.github.io/umriss-ui/core/popover/#width-from-anchor) and 2 more |

Also takes every attribute of `<svg>`.

## Known limits

- One shape and no figure: for how far a job has come, use a [ProgressBar](https://romanhaendler.github.io/umriss-ui/core/progressbar/).

What umriss deliberately does not build, and why: [ADR-0032](https://romanhaendler.github.io/umriss-ui/what-umriss-ui-is-not/).
