A skeleton is the shape of content that has not arrived. Its job is not to say "loading", a
`spinner` does that in a fraction of the room, but to teach the layout before the text
does: the heading is here, the avatar is here, three lines of body sit there. By the time
the content lands, the reader's eye already knows where to look, and the region does not
change size underneath them.

It is silent by default, and deliberately: `aria-hidden` on a placeholder that carries no
information is the honest markup. When the wait itself needs announcing, `isBusy` with a
`label` turns the same placeholder into a labelled busy status.

```tsx demo="feedback/skeleton-preview"
```

## Usage

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

<Skeleton className="h-4 w-64" />
```

The component has no size axis, because a placeholder is only ever the size of the thing it
stands in for. Sizing belongs in `className`, `h-4` for a line of body text, `h-6` for a
heading, `h-28` for a picture, while `radius` and the shimmer resolve through the cascade.
Its surface is the theme's secondary token, so it follows the active theme rather than
carrying a grey of its own.

## Shapes

A placeholder earns its name by matching the content. Round it like the thing it replaces,
`sm` for a line of text, `full` for an avatar, `lg` for a card, and compose the group out of
the same layout the real rows will use, so nothing shifts when they arrive.

```tsx demo="feedback/skeleton-shapes"
```

A single grey bar is not a skeleton; it is a progress bar without the progress. The value is
in the arrangement: an avatar beside two lines tells the reader that a person is coming, and
a full-width block tells them a picture is.

## Announcing the wait

`isBusy` changes what the placeholder is: it drops `aria-hidden`, becomes a `role="status"`
with `aria-busy="true"`, and renders `label` in a visually hidden span so the status has
something to say. `label` defaults to `Loading`, which is rarely the most useful sentence,
name the thing that is loading.

```tsx demo="feedback/skeleton-busy"
```

One busy placeholder per group is enough. Each one is its own status region, so a group of
five placeholders marked `isBusy` announces the same sentence five times; mark the first and
leave its neighbours decorative. And because a live region reports a change rather than a
presence, a skeleton that is already on the page when the reader arrives announces nothing
until it changes, which is exactly what you want for first paint.

## Props

```props Skeleton
```

`radius` is registered as `sm` and `isAnimated` as `true`, so both answer from the instance,
then from `components.skeleton`, then from the framework's fallback. As with the alert, the
registered radius means the application's `defaultRadius` does not reach a skeleton: a
product that rounds everything else still gets `sm` placeholders until it states
`components.skeleton.radius`. `isBusy` and `label` are instance-only, because they describe
this region's wait rather than the design.

The shimmer is applied through `motion-safe`, so a reader who has asked their system for
less motion sees a still placeholder with no script involved. `isAnimated={false}` turns it
off everywhere, for a product that would rather not animate a placeholder at all.

## Accessibility

- By default the placeholder is `aria-hidden="true"` with no role and no name: it stands in
  for content, and it is not content. Nothing about it is announced or reachable.
- `isBusy` with a `label` exposes it as `role="status"` with `aria-busy="true"`, and the
  label is rendered as text inside the element rather than as an attribute, so the status
  has an accessible name the reader hears. Mark one placeholder in a group, not all of them.
- Match the placeholder's size to the content that replaces it. The room it claims is the
  room the reader has already learned to expect; a region that grows when the data arrives
  undoes the point of the placeholder.
- The pulse is inside the reduced-motion-safe variant, so a reader who asked for less motion
  keeps the shape and loses the movement. The information is the layout, not the shimmer.

Skeletons, spinners and status regions make different amounts of noise on purpose; the
reasons are collected in [Announcements](/concepts/announcements).

## Related

```related spinner, loading-state, card
```
