# 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.36.0/src/tokens/tokens.css">
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/@lo-tek/interfaces@0.36.0/src/components/css/spinner/spinner.css">
<!-- Or everything in one file: https://cdn.jsdelivr.net/npm/@lo-tek/interfaces@0.36.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" })} aria-hidden="true" />
```

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 applications" }))
```

`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. See [Type-safe use](/type-safe-use.md).

| `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>
Fetching your applications
</p>
```

If the text is visible, set `aria-hidden="true"` on the ring. If the ring also has a name, the wait is otherwise 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 applications"></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>
  Sending application
</button>
```

Let the button text change from “Send application” to “Sending application”. 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. “Fetching your applications” 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.
