# Spinner

A ring of eight lamps lit in turn, a chase round a dial: each lamp is a dim cap with its light laid over it, and the light fades as the chase passes on, leaving a short tail. Only the light’s opacity moves. Amber, the lamp for work in hand. Under reduced motion it holds still on one lit lamp at the top, and the words still say it is busy.

Kit part · Free · https://tactileui.dev/parts/spinner

- Element: `<span class="tui-spinner" role="status">`
- Needs: `kit.css` and `kit.mjs` ([how to install the kit](https://tactileui.dev/docs/installation#the-kit))
- Categories: Loaders
- Finishes: Aluminium, Graphite, Oak, Walnut, Porcelain, Black, Daylight, Night, Paper, Phosphor, E-paper, AMOLED
- Volumes: Tactile, Quiet

## Variants

### Default · 24px

```html
<span class="tui-spinner" role="status"><span class="tui-spinner__ring" aria-hidden="true"><i></i><i></i><i></i><i></i><i></i><i></i><i></i><i></i></span><span class="tui-spinner__label">Loading</span></span>
```

### Small · --sm

```html
<span class="tui-spinner tui-spinner--sm" role="status"><span class="tui-spinner__ring" aria-hidden="true"><i></i><i></i><i></i><i></i><i></i><i></i><i></i><i></i></span><span class="tui-spinner__label">Loading</span></span>
```

### Large · --lg

```html
<span class="tui-spinner tui-spinner--lg" role="status"><span class="tui-spinner__ring" aria-hidden="true"><i></i><i></i><i></i><i></i><i></i><i></i><i></i><i></i></span><span class="tui-spinner__label">Loading</span></span>
```

### With words on screen · --labelled

```html
<span class="tui-spinner tui-spinner--labelled" role="status"><span class="tui-spinner__ring" aria-hidden="true"><i></i><i></i><i></i><i></i><i></i><i></i><i></i><i></i></span><span class="tui-spinner__label">Searching the archive</span></span>
```

### Lamp colours · data-lamp

```html
<div style="display:flex;gap:20px;flex-wrap:wrap"><span class="tui-spinner tui-spinner--lg" role="status" data-lamp="green"><span class="tui-spinner__ring" aria-hidden="true"><i></i><i></i><i></i><i></i><i></i><i></i><i></i><i></i></span><span class="tui-spinner__label">Working (green)</span></span><span class="tui-spinner tui-spinner--lg" role="status" data-lamp="red"><span class="tui-spinner__ring" aria-hidden="true"><i></i><i></i><i></i><i></i><i></i><i></i><i></i><i></i></span><span class="tui-spinner__label">Working (red)</span></span><span class="tui-spinner tui-spinner--lg" role="status" data-lamp="blue"><span class="tui-spinner__ring" aria-hidden="true"><i></i><i></i><i></i><i></i><i></i><i></i><i></i><i></i></span><span class="tui-spinner__label">Working (blue)</span></span><span class="tui-spinner tui-spinner--lg" role="status" data-lamp="white"><span class="tui-spinner__ring" aria-hidden="true"><i></i><i></i><i></i><i></i><i></i><i></i><i></i><i></i></span><span class="tui-spinner__label">Working (white)</span></span></div>
```

### In a key

```html
<button class="tui-key" type="button" aria-disabled="true"><span class="tui-spinner tui-spinner--sm tui-spinner--labelled"><span class="tui-spinner__ring" aria-hidden="true"><i></i><i></i><i></i><i></i><i></i><i></i><i></i><i></i></span><span class="tui-spinner__label">Signing in</span></span></button>
```

## Every state

- Small
- Default
- Large
- Labelled
- Green

Hover, pressed and focus are forced with `.is-hover`, `.is-down` and `.is-focus`; checked and disabled are real.

## API

| Name | Kind | Values | What it does |
| - | - | - | - |
| span.tui-spinner[role="status"] | element | with a label | The spinner. A status region, so its label is said when it appears. Add data-lamp to light the ring green, red, blue or white (amber is the default). |
| .tui-spinner__ring[aria-hidden="true"] | element | eight | The drawing: eight empty i elements, one lamp each. Hidden from a screen reader. |
| .tui-spinner__label | element | the words | What is going on. Always in the page for a screen reader; drawn on screen with --labelled. |
| --sm · --lg · --labelled | modifier | 16px · 40px · words shown | The ring’s size, and whether the label is drawn beside it. Set --sp for any other size. |
| spinnerHTML({ label, size, lamp, labelled, status }) | function | pure | The markup of one. Pass status: false inside a region that is already a status. |
| data-paused | attribute | set by kit.mjs | Holds the chase while the spinner is off screen. Set it yourself in a hidden pane. |

## Markup

```html
<span class="tui-spinner tui-spinner--labelled" role="status">
  <span class="tui-spinner__ring" aria-hidden="true">
    <i></i><i></i><i></i><i></i><i></i><i></i><i></i><i></i>
  </span>
  <span class="tui-spinner__label">Importing 3 of 14 files</span>
</span>

<!-- Without the words on screen, they are still said: drop --labelled -->
```

```js
import { spinnerHTML } from '/kit/kit.mjs';

// The markup of one, so a script does not have to type eight <i>.
el.innerHTML = spinnerHTML({ label: 'Importing', size: 'lg', labelled: true });
spinnerHTML({ label: 'Saving', status: false });     // inside a region that already says it
```

The demo on the page:

```html
<div class="tui-panel" style="width:min(100%,460px);display:grid;gap:16px;text-align:left"><div class="tui-panel__head" style="margin-bottom:0"><span class="tui-label">Import · Lode</span><span class="tui-status"><span class="tui-lamp" data-on></span>Working</span></div>
  <div style="display:grid;gap:14px">
    <div><span class="tui-spinner tui-spinner--lg tui-spinner--labelled" role="status"><span class="tui-spinner__ring" aria-hidden="true"><i></i><i></i><i></i><i></i><i></i><i></i><i></i><i></i></span><span class="tui-spinner__label">Importing 3 of 14 files</span></span></div>
    <div class="tui-progress" style="--v:.21" aria-hidden="true"></div>
    <span class="tui-muted" style="font-size:var(--tui-t-small)">You can leave this page; the import carries on.</span>
  </div></div>
```

## Usage notes

Watch the chase: one lamp is lit and the next few after it fade. Turn on reduced motion in your system settings and it stops with the top lamp lit. The words beside the ring are the message; the ring is only drawn. The panel is a status region, so a screen reader says what is going on without being asked.

## Accessibility

- It is `role="status"` with its words in `.tui-spinner__label`, so a screen reader says “Importing 3 of 14 files” politely when it appears; the eight lamps are `aria-hidden`.
- Busy is said in words and shown by the chase, never by colour alone, and the words are what to put on screen when the wait may be long.
- Under reduced motion the chase stops and the lamp at the top stays lit, so it is a steady lamp that still says busy. Nothing else moves, and it never speeds up or bounces.
- The chase runs a second at a time and is the sign that something is loading, not decoration; it pauses while it is off screen, and it should be taken out of the page the moment the work ends.
- It is not a control: it takes no focus and no pointer, and on a phone it is the same size.

## How it’s built

Eight lamps on a circle, each a dim cap with a lit layer on top of it. Every lit layer runs the same short fade, 960 milliseconds long, started a step later than its neighbour, so the lit one appears to travel and the lamps just behind it are still dying away. The lamps are placed with a direction and a radius rather than rotated, so each one’s shine stays on its upper left. In the finishes that draw their lamps as square pixels or printed rings, the spinner’s lamps are drawn the same way.

## Rules

Show that something is working when no one can say how long it will take: a ring of lamps chased in turn, with a line of words that is the real message.

The full rules are in `spinner.rules.md` in the brain, which ships with the Kit.

## Related parts

- [Lamp](https://tactileui.dev/parts/lamp): The only colour in the kit: a lamp that is on. Green is on, amber is waiting, red is recording or failing, blue is linked, white is standing by. Always next to words.
- [Skeleton](https://tactileui.dev/parts/skeleton): The shape of a page before its data arrives, drawn as the unprinted plate: blank engraved bars where the words will print. Nothing on it moves. One amber lamp beside “Loading” is the only sign of work.
- [Progress](https://tactileui.dev/parts/progress): Progress through a task: ink filling a groove, never a coloured bar.
- [Loading overlay](https://tactileui.dev/parts/loading-overlay): A pane of smoked glass laid over a panel while it works. The panel underneath keeps every pixel of its place, dimmed and a little out of focus, with one plate in the middle carrying a spinner and a line of words. Set aria-busy on the panel to lower the glass; what is underneath goes inert, so nothing in it can be pressed or tabbed to until the glass lifts.
