# Skeleton

A grey pulsing placeholder that holds the shape of content that is still loading.

- **Import:** `import { Skeleton } from "@booleanpress/ui/skeleton"`
- **Page:** <https://ui.booleanpress.com/components/skeleton> · @booleanpress/ui 0.1.0

## Usage

A skeleton is an empty `div` with a pulse. You size and shape it with classes, so it matches the content that will replace it.

```tsx
import { Skeleton } from "@booleanpress/ui/skeleton"

export function LogLoading() {
  return (
    <div aria-busy="true" className="flex flex-col gap-2">
      <p role="status" className="sr-only">Loading the email log</p>
      <Skeleton className="h-4 w-full" />
      <Skeleton className="h-4 w-2/3" />
    </div>
  )
}
```

Use it for the first load only. When a table already has rows and refreshes, keep the rows and dim them, so they do not jump. Match the placeholder's size to the real content so the page does not shift when it arrives.

## Examples

### Lines

Three text lines of different widths.

```tsx
import { Skeleton } from "@booleanpress/ui/skeleton"

export default function SkeletonLines() {
  return (
    <div aria-busy="true" className="flex w-full max-w-sm flex-col gap-2">
      <p role="status" className="sr-only">
        Loading the message
      </p>
      <Skeleton className="h-4 w-full" />
      <Skeleton className="h-4 w-11/12" />
      <Skeleton className="h-4 w-2/3" />
    </div>
  )
}
```

### Profile

A round placeholder for an avatar beside two lines.

```tsx
import { Skeleton } from "@booleanpress/ui/skeleton"

export default function SkeletonProfile() {
  return (
    <div aria-busy="true" className="flex w-full max-w-sm items-center gap-4">
      <p role="status" className="sr-only">
        Loading the person
      </p>
      <Skeleton className="size-10 shrink-0 rounded-full" />
      <div className="flex flex-1 flex-col gap-2">
        <Skeleton className="h-4 w-1/2" />
        <Skeleton className="h-3 w-3/4" />
      </div>
    </div>
  )
}
```

### Table rows

Rows of two cells, with a button that swaps to the loaded content.

```tsx
import { useState } from "react"
import { Button } from "@booleanpress/ui/button"
import { Skeleton } from "@booleanpress/ui/skeleton"

const LOGS = [
  { to: "ada@example.com", subject: "Welcome" },
  { to: "grace@example.com", subject: "Password reset" },
  { to: "linus@example.com", subject: "Invoice 1042" },
]

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

  return (
    <div className="flex w-full max-w-md flex-col gap-4">
      <div aria-busy={loading} className="flex flex-col gap-3">
        {loading ? (
          <>
            <p role="status" className="sr-only">
              Loading the email log
            </p>
            {LOGS.map((log) => (
              <div key={log.to} className="flex items-center justify-between gap-4">
                <Skeleton className="h-4 w-40" />
                <Skeleton className="h-4 w-24" />
              </div>
            ))}
          </>
        ) : (
          LOGS.map((log) => (
            <div key={log.to} className="flex items-center justify-between gap-4 text-sm">
              <span>{log.to}</span>
              <span className="text-muted-foreground">{log.subject}</span>
            </div>
          ))
        )}
      </div>
      <Button variant="outline" size="sm" className="self-start" onClick={() => setLoading((value) => !value)}>
        {loading ? "Show the loaded log" : "Show the loading state"}
      </Button>
    </div>
  )
}
```

## Accessibility

**Semantics.** A `div` with no role. It is invisible to assistive technology.

**Labels.** Say that something is loading yourself: a visually hidden `role="status"` message, and `aria-busy="true"` on the region being filled, as the examples do.

**Focus.** A skeleton takes no focus and has no keyboard behaviour of its own, so it has no keyboard rows.

**Known limits.**

- The pulse is `animate-pulse`. `theme.css` stops it under `prefers-reduced-motion`, and the grey block stays.
- A skeleton alone tells a screen-reader user nothing. Never ship it without the status text.

### Keyboard

| Key | Behaviour |
| --- | --- |

## API

### Skeleton

Renders a `div` and passes it every other prop.

Every part takes `className`, merged with its defaults by `cn()`, and `ref`, which reaches the element it renders.

**Data attributes:** `data-slot="skeleton"` (Skeleton).

## Theming

The fill is `--accent`.

| Token | Used for |
| --- | --- |
| `--accent` | background |
