# Spinner

> A wait indicator for something in progress. CSS component with one class and no JavaScript.

A ring that spins while something loads. If you know how long it takes, or how far it has got, [Progress](/components/progress.md) is better. This one only says that something is in progress.

## How to use it

**Plain HTML**

```html
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/@lo-tek/interfaces@0.40.0/src/tokens/tokens.css">
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/@lo-tek/interfaces@0.40.0/src/components/css/spinner/spinner.css">
<!-- Or everything in one file: https://cdn.jsdelivr.net/npm/@lo-tek/interfaces@0.40.0/dist/lotek.css -->
```

```html
<span class="lt-spinner" data-size="small" aria-hidden="true"></span>
<span class="lt-spinner" aria-hidden="true"></span>
<span class="lt-spinner" data-size="large" aria-hidden="true"></span>
```

**TypeScript**

```ts
import "@lo-tek/interfaces/tokens.css"
import "@lo-tek/interfaces/spinner.css"
```

`lt` is used in two ways, and which one depends on whether you create the element or find it.

**If you create the markup,** you spread the attributes in. That is the route in React, Astro and anything else that renders:

```tsx
import { lt } from "@lo-tek/interfaces/react"


<span {...lt.spinner({ size: "small" })} />
```

The import goes to `/react` here, since the block is JSX. From there the keys are called `className` and `htmlFor`. In Astro and anything else without React you import `@lo-tek/interfaces` and get `class` and `for`.

**If the element is already there,** you set them on it. That is the route when you enhance HTML a server or a template has sent:

```ts
import { lt } from "@lo-tek/interfaces"


const ring = document.querySelector<HTMLElement>("#waiting")


lt.setAttributes(ring, lt.spinner({ size: "small", label: "Loading sectors" }))
```

`label` gives both `role="status"` and `aria-label`, since the ring has no name without both. If you leave it out, the ring is just a shape, and `lt.spinner()` hides it from screen readers with `aria-hidden="true"`. So give the name through `label`, never as `aria-label` by hand: a hand-written name would sit on a ring that is hidden. See [Type-safe use](/type-safe-use.md).

**kotlinx.html**

```kotlin
// Once for the whole app, in the page template: every stylesheet, from the WebJar.
head { link(rel = "stylesheet", href = LotekWebJar.CSS) }
```

On a Kotlin or Java server, the same with kotlinx.html and `lo-tek-kotlinx-html`. The builder functions are in `Lt`, with an enum for each list of values:

```kotlin
import kotlinx.html.*
import no.lotek.*
import no.lotek.html.lt


fun FlowContent.spinners() {
    span { lt(Lt.spinner(size = Size.SMALL)) }
    span { lt(Lt.spinner()) }
    span { lt(Lt.spinner(size = Size.LARGE)) }
}
```

| `data-size` | Used for |
| - | - |
| `small` | Inside a button or a table row |
| *(omitted)* | Next to a text |
| `large` | In the middle of a page or in a panel that is loading |

## With text beside it

Someone listening to the page gets nothing from a ring. Let a visible text carry the message. The ring can get a name with `aria-label`, but it is not certain to be announced by itself; see below.

**Plain HTML**

```html
<p role="status">
<span class="lt-spinner" data-size="small" aria-hidden="true"></span>
Tracing the fault in sector 9
</p>
```

If the text is visible, the ring has no `label`, and `lt.spinner()` then writes `aria-hidden="true"` on it. If the ring also had a name, the wait would be announced twice.

The paragraph is announced when the text is inserted into it. So leave `<p role="status">` empty in the page beforehand, and write the ring and the text into it when the wait begins.

The ring is `inline-block`, so it sits in line with the text by itself. If you want them on the same baseline with a fixed gap, that is the app’s layout: `display: flex`, `align-items: center` and `gap` on the paragraph.

If the ring stands alone, it gets the role and the name itself:

```html
<span class="lt-spinner" role="status" aria-label="Loading sectors"></span>
```

That gives the ring a name when someone looks for it, but it is not certain to be announced by itself. A region with `role="status"` announces changes to its content, and the ring has no content. If it matters that the wait is reported, use the paragraph with text above, in a status element that is in the page before the wait starts.

## Inside a button

```html
<button class="lt-button" disabled>
  <span class="lt-spinner" data-size="small" aria-hidden="true"></span>
  Restarting relay 14
</button>
```

Let the button text change from “Restart relay 14” to “Restarting relay 14”. Then the button says what is happening, also for someone who does not see the ring.

## Classes you can style

| Class | What it is |
| - | - |
| `.lt-spinner` | The ring. The size comes from `data-size`. |

| Variable | Default |
| - | - |
| `--lt-spinner-size` | `var(--lt-spacing-6)` |
| `--lt-spinner-thickness` | `2px` |

The variables apply to the default size. `data-size="small"` and `data-size="large"` set their own size, and `large` its own thickness as well.

If the user has asked for reduced motion in their system, the ring spins more slowly. It does not stop completely: an indicator that stands still no longer says that something is in progress.

See [Customising](/customising.md) for how the variables and the layer work.

## Accessibility

- Wait a little before you show it. A ring that flashes past in 100 milliseconds is more disturbing than nothing.
- Say what is loading, not just that something is loading. “Tracing the fault in sector 9” is worth more than “Loading”.
- When the wait is over, move focus to where the result appeared, or report it in a `role="status"` element. Otherwise the user is left without knowing that anything happened.
