A region that is on its way is not empty, and saying so would be wrong twice over: the reader
would be told there is nothing, and the page would change shape when the content arrived.
`LoadingState` is the region's own presentation while it waits, an indicator, a label that names
what is loading, and a claim on the room the content will take.

It is the region rather than an ornament inside one: while a list loads, this is what the list is.
A small indeterminate wait inside a control or a row is `Spinner`, and the known shape of what is
coming is `Skeleton`.

```tsx demo="feedback/loading-state-preview"
```

## Usage

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

<LoadingState
  label="Loading invoices"
  description="Fetching this quarter's invoices."
/>
```

`label` says what is being loaded, not that something is. It takes a node rather than a string, so
the sentence can carry an emphasised word or a glyph beside the text, an icon on its own names
nothing, because the text is what a reader hears. It is registered as `Loading`, which is a
placeholder for the one sentence the reader cannot get from the page, a region that says "Loading"
repeats what the spinner already said. `description` is the second line, for where the content is
coming from or how long it tends to take.

`indicator` replaces the framework's spinner, which is the seam for a determinate progress bar or a
product's own animation. It takes a node, so a percentage goes there while the label keeps naming
the thing rather than changing on every tick.

## The room it claims

`minHeight` is a step of the shared spacing scale rather than a pixel value: `xs` claims `4rem`,
`sm` `6rem`, `md` `12rem`, `lg` `18rem`, `xl` `24rem` and `2xl` the height of the viewport, with
`none` claiming nothing.

A claim is a floor, not a height, which is the part worth understanding: the region is never
smaller than its own padding and indicator, so a claim below that changes nothing at all. What a
claim buys is the arrival, the content replaces the state in room already taken, so the page below
does not move when it lands.

Worth knowing when you wire it: the registered claim is `md`, one step above the room the
registered `md` density already takes with its padding and its indicator, so the default pairing
binds, the region reserves that much room instead of sizing itself to the spinner. A claim at or
below what the density already occupies reserves nothing extra, so the two are worth changing
together: raise `minHeight` for a taller wait, or take the density down when the region is a short
one.

```tsx demo="feedback/loading-state-room"
```

## It is the region

The state takes the space the content will take, which is why it is drawn *instead of* the region
rather than inside it. `panel` decides whether it also draws the surface: `true` for a region inside
a dashboard or a card, where the surrounding surface is what the content will fill; `false` for a
page that is loading as a whole and has no surface to replace.

```tsx demo="feedback/loading-state-region"
```

## Props

```props LoadingState
```

`label`, `size`, `panel` and `minHeight` resolve instance → `components.loadingstate` → the
framework's fallback, registered as `Loading`, `md`, `false` and `md`. A product that always loads
the same thing renames the label once, in configuration, and every region that does not state its
own says it.

`description`, `indicator`, `children` and `as` are instance-only: what is loading, and what to put
in its place, is a statement about one region. `children` is the slot below the label, for a cancel
control or a "this is taking longer than usual" note.

## Accessibility

- The region draws its own `role="status"`; the announcement is not an option, the way it is on
  `EmptyState` and `ErrorState`. A load in progress is always a change worth reporting politely,
  and it always waits for the reader to finish what they are on.
- The spinner is `aria-hidden`, so the label's text is the whole announcement. That is why the label
  has to name the thing rather than the fact: nothing else in the region will be heard.
- `description` is inside the region and is read with it, so keep it to a sentence. Anything passed
  as `indicator` is announced too, which is what makes a real percentage worth passing and a
  decorative animation worth leaving out.
- Nothing the state draws is focusable, a cancel control passed as `children` is the reader's own
  choice, and nothing moves focus. A wait reports itself; it does not take the reader somewhere,
  and it does not stop them reading the rest of the page while it finishes.

The channels, and why a busy region keeps its size, are
[Announcements](/concepts/announcements).

## Related

```related empty-state, error-state, data-table
```
