# Skeleton

Display a skeleton whilst another component is loading.

---

## Default with set width

```tsx
import { Skeleton } from '@vercel/geistcn/components';
import type { JSX } from 'react';

export function Component(): JSX.Element {
  return <Skeleton width={160} />;
}
```

## Default with box height

```tsx
import { Skeleton } from '@vercel/geistcn/components';
import type { JSX } from 'react';

export function Component(): JSX.Element {
  return <Skeleton boxHeight={42} width={160} />;
}
```

## Wrapping children

If you do not pass a fixed size, it will be calculated automatically.

```tsx
import { Button, Skeleton } from '@vercel/geistcn/components';
import type { JSX } from 'react';

export function Component(): JSX.Element {
  return (
    <div className="flex flex-col items-start justify-start gap-4 flex-initial">
      <Skeleton>
        <Button>Hidden by skeleton</Button>
      </Skeleton>

      <Skeleton show={false}>
        <Button>Not hidden by skeleton</Button>
      </Skeleton>
    </div>
  );
}
```

## Wrapping children with fixed size

The skeleton will hide when children are not null, but the size is retained.

```tsx
import { Button, Skeleton } from '@vercel/geistcn/components';
import type { JSX } from 'react';

export function Component(): JSX.Element {
  return (
    <div className="flex flex-col items-start justify-start gap-4 flex-initial">
      <Skeleton height={100} width="100%">
        {null}
      </Skeleton>

      <Skeleton height={100} width="100%">
        <Button>Not hidden by Skeleton</Button>
      </Skeleton>
    </div>
  );
}
```

## Pill

```tsx
import { Skeleton } from '@vercel/geistcn/components';
import type { JSX } from 'react';

export function Component(): JSX.Element {
  return <Skeleton pill width={48} />;
}
```

## Rounded

```tsx
import { Skeleton } from '@vercel/geistcn/components';
import type { JSX } from 'react';

export function Component(): JSX.Element {
  return <Skeleton boxHeight={48} height={48} rounded width={48} />;
}
```

## Squared

```tsx
import { Skeleton } from '@vercel/geistcn/components';
import type { JSX } from 'react';

export function Component(): JSX.Element {
  return <Skeleton boxHeight={48} height={48} squared width={48} />;
}
```

## No animation

```tsx
import { Skeleton } from '@vercel/geistcn/components';
import type { JSX } from 'react';

export function Component(): JSX.Element {
  return (
    <Skeleton animated={false} height={100} width="100%">
      {null}
    </Skeleton>
  );
}
```

## Button

```tsx
import { Button, Skeleton } from '@vercel/geistcn/components';
import type { JSX } from 'react';

export function Component(): JSX.Element {
  return (
    <div className="flex flex-col gap-4">
      <div className="flex flex-col gap-2">
        <p className="text-label-14">Without button prop (default):</p>
        <Skeleton height={32} width={120}>
          <Button>Loading...</Button>
        </Skeleton>
      </div>
      <div className="flex flex-col gap-2">
        <p className="text-label-14">
          With button prop (extends animation by 1px):
        </p>
        <Skeleton button height={32} width={120}>
          <Button>Loading...</Button>
        </Skeleton>
      </div>
      <div className="flex flex-col gap-2">
        <p className="text-label-14">Multiple buttons loading:</p>
        <div className="flex gap-3">
          <Skeleton button>
            <Button>Save</Button>
          </Skeleton>
          <Skeleton button>
            <Button variant="secondary">Cancel</Button>
          </Skeleton>
        </div>
      </div>
    </div>
  );
}
```

## Best Practices

### When to use

* Show a Skeleton when async data fills a known layout: table rows, card grids, profile blocks, sidebars.
* For a single in-flight action, use `Spinner`; for an indeterminate inline wait, use `LoadingDots`; for known progress, use `Progress`.
* Don’t use Skeleton as permanent decoration or as a placeholder for empty states. When there’s no data to load, render an `EmptyState`.

### Behavior

* Set `width` and `height` to match the final content so the layout doesn’t shift when data resolves. A 200×20 block becoming an 80×16 string reads as a glitch.
* Pick `pill`, `rounded`, or `squared` to mirror the eventual element’s shape (avatars `pill`, buttons and chips `rounded`, image tiles `squared`).
* When the skeleton wraps children, keep dimensions stable so the reveal swap doesn’t reflow surrounding content.

### Accessibility

* Wrap the loading region in `aria-busy="true"` and announce completion with `aria-live="polite"` on the destination container, not the skeleton itself.
* Disable the shimmer with the `no animation` variant on low-power surfaces and respect `prefers-reduced-motion`.
* Skeletons are decorative; avoid placing focusable controls inside them while loading.
