# Skeleton

> Package `@umriss-ui/core`, version 0.40.0. Demo page: <https://romanhaendler.github.io/umriss-ui/core/skeleton/>. Every page in one line: [llms.txt](https://romanhaendler.github.io/umriss-ui/core/llms.txt); every page in full: [llms-full.txt](https://romanhaendler.github.io/umriss-ui/core/llms-full.txt).

Grey shapes that stand where content is still loading and hold the height it will have (also called a placeholder or shimmer). Reach for it when you know the layout of what is coming, so nothing jumps when it arrives.

```ts
import { Skeleton } from "@umriss-ui/core";
```

Demo page: https://romanhaendler.github.io/umriss-ui/core/skeleton/

A skeleton is hidden from screen readers. Mark the region that is loading with `aria-busy`, and say in words what loads where that matters.

## Examples

### Lines of text to come

A line per line of text, the last one shorter, so the placeholder has the height the text will have.

```tsx
import { Skeleton, Stack } from "@umriss-ui/core";

export default function Lines() {
  return (
    <Stack gap={2} style={{ maxWidth: 420 }}>
      <Skeleton />
      <Skeleton />
      <Skeleton width="60%" />
    </Stack>
  );
}
```

### Shapes

`circle`, `width` and `height` give it the shape of what comes – an avatar, a name, a line beneath it.

```tsx
import { Skeleton, Stack } from "@umriss-ui/core";

export default function Shapes() {
  return (
    <Stack direction="row" gap={3} align="center" style={{ maxWidth: 420 }}>
      <Skeleton circle width={32} height={32} />
      <Stack gap={2} style={{ flex: 1 }}>
        <Skeleton width="55%" height={14} />
        <Skeleton width="35%" height={10} />
      </Stack>
    </Stack>
  );
}
```

### Hold a list's place while it loads

The placeholder rows are as tall as the rows that replace them, so nothing below jumps when the data arrives.

```tsx
import { useState } from "react";
import { Button, Skeleton, Stack, Text } from "@umriss-ui/core";

const PEOPLE = [
  { name: "Maya Lindgren", role: "Product manager · Web" },
  { name: "Arjun Mehta", role: "Developer · Web" },
  { name: "Noah Fischer", role: "Designer · Web" },
];

function Avatar({ name }: { name: string }) {
  return (
    <span
      aria-hidden="true"
      style={{
        width: 32,
        height: 32,
        borderRadius: "50%",
        display: "grid",
        placeItems: "center",
        background: "var(--u-color-surface-sunken)",
        fontSize: "var(--u-text-xs)",
      }}
    >
      {name.split(" ").map((part) => part[0])}
    </span>
  );
}

export default function AListLoading() {
  const [loading, setLoading] = useState(true);

  return (
    <Stack gap={4} style={{ maxWidth: 420 }}>
      <Stack gap={3} aria-busy={loading}>
        {PEOPLE.map((person) => (
          <Stack key={person.name} direction="row" gap={3} align="center" style={{ minHeight: 36 }}>
            {loading ? (
              <>
                <Skeleton circle width={32} height={32} />
                <Stack gap={2} style={{ flex: 1 }}>
                  <Skeleton width="50%" height={14} />
                  <Skeleton width="35%" height={10} />
                </Stack>
              </>
            ) : (
              <>
                <Avatar name={person.name} />
                <Stack gap={1}>
                  <Text size="sm" weight="medium">
                    {person.name}
                  </Text>
                  <Text size="xs" tone="muted">
                    {person.role}
                  </Text>
                </Stack>
              </>
            )}
          </Stack>
        ))}
      </Stack>
      <Button size="sm" onClick={() => setLoading((l) => !l)} style={{ alignSelf: "flex-start" }}>
        {loading ? "Show the loaded rows" : "Show the placeholders"}
      </Button>
    </Stack>
  );
}
```

## When to use something else

- The layout of what is coming is unknown → Spinner
- You can say how far the loading has come → ProgressBar

## Keyboard

The keys of [Button](https://romanhaendler.github.io/umriss-ui/core/button/#keyboard-button) apply here.

## Accessibility

Every skeleton is `aria-hidden`: a screen reader meets nothing where it stands, and nothing is announced when the content replaces it. The region that loads carries `aria-busy` from you, and a word where the wait matters.

With reduced motion the shimmer stops, and the shape stands in a still, faint grey.

## API

### `SkeletonProps`

| Prop | Type | Default | Description |
|---|---|---|---|
| `width` | `string \| number` | `"100%"` | Width, e.g. "120px" or "60%".<br>Shown in: [Configurator](https://romanhaendler.github.io/umriss-ui/core/skeleton/#configurator-skeleton), [Lines of text to come](https://romanhaendler.github.io/umriss-ui/core/skeleton/#lines), [Shapes](https://romanhaendler.github.io/umriss-ui/core/skeleton/#shapes) and 2 more |
| `height` | `string \| number` | `"12px"` | Height, e.g. "12px" or 16.<br>Shown in: [Configurator](https://romanhaendler.github.io/umriss-ui/core/skeleton/#configurator-skeleton), [Shapes](https://romanhaendler.github.io/umriss-ui/core/skeleton/#shapes), [Hold a list's place while it loads](https://romanhaendler.github.io/umriss-ui/core/skeleton/#a-list-loading) |
| `circle` | `boolean` | `false` | Circular shape, e.g. for an avatar placeholder.<br>Shown in: [Configurator](https://romanhaendler.github.io/umriss-ui/core/skeleton/#configurator-skeleton), [Shapes](https://romanhaendler.github.io/umriss-ui/core/skeleton/#shapes), [Hold a list's place while it loads](https://romanhaendler.github.io/umriss-ui/core/skeleton/#a-list-loading) |
| `ref` | `Ref<HTMLSpanElement>` | — | Receives the component's root element. From `RefProp`.<br>Shown in: [A row that holds still (Sizes)](https://romanhaendler.github.io/umriss-ui/core/sizes/#a-row-that-holds-still), [An anchored surface (Popover)](https://romanhaendler.github.io/umriss-ui/core/popover/#anchored-surface), [Take the width of the trigger (Popover)](https://romanhaendler.github.io/umriss-ui/core/popover/#width-from-anchor) and 2 more |

Also takes every attribute of `<span>`.

## Known limits

- It draws a shape and nothing else; the rows, cards and layout it stands in for are yours.

What umriss deliberately does not build, and why: [ADR-0032](https://romanhaendler.github.io/umriss-ui/what-umriss-ui-is-not/).
