# Skeleton

Shows a placeholder while content is loading.

```tsx
import * as stylex from "@stylexjs/stylex"
import { Skeleton } from "@/components/ui/skeleton"
import { HStack, VStack } from "@/components/ui/stack"
import { radius, space } from "@/theme/tokens.stylex"

const styles = stylex.create({
  avatar: { width: space.s12, height: space.s12, borderRadius: radius.full },
  title: { width: "16rem", height: space.s4 },
  line: { width: "12rem", height: space.s4 },
})

export default function SkeletonDemo() {
  return (
    <HStack gap={4} align="center">
      <Skeleton xstyle={styles.avatar} />
      <VStack gap={2}>
        <Skeleton xstyle={styles.title} />
        <Skeleton xstyle={styles.line} />
      </VStack>
    </HStack>
  )
}
```

## Installation

1. Install the following dependencies:

   ```bash
   npm install @base-ui/react @stylexjs/stylex clsx
   ```

2. Set up the token layer and the StyleX compiler, if you have not already. See
   [Installation](https://carbon.style/docs/installation).

3. Copy and paste the following code into your project.

   ```tsx title="components/ui/skeleton.tsx"
   "use client"

   import { useRender } from "@base-ui/react/use-render"
   import * as stylex from "@stylexjs/stylex"
   import type { StyleXStyles } from "@stylexjs/stylex"
   import { clsx } from "clsx"

   import { color, ease, radius } from "@/theme/tokens.stylex"

   const skeletonSheen = "light-dark(oklch(1 0 0 / 70%), oklch(1 0 0 / 6%))"

   const sweep = stylex.keyframes({
     from: { backgroundPosition: "-130vw 0" },
     to: { backgroundPosition: "30vw 0" },
   })

   const pulse = stylex.keyframes({
     "0%": { opacity: 1 },
     "50%": { opacity: 0.5 },
     "100%": { opacity: 1 },
   })

   const styles = stylex.create({
     base: {
       borderRadius: radius.md,
       backgroundColor: color.muted,
       backgroundImage: {
         default: `linear-gradient(90deg, transparent 35%, ${skeletonSheen} 50%, transparent 65%)`,
         "@media (prefers-reduced-motion: reduce)": "none",
       },
       backgroundSize: "200vw 100%",
       backgroundRepeat: "no-repeat",
       backgroundAttachment: "fixed",
       animationName: {
         default: sweep,
         "@media (prefers-reduced-motion: reduce)": pulse,
       },
       animationDuration: { default: "3s", "@media (prefers-reduced-motion: reduce)": "2s" },
       animationTimingFunction: {
         default: "linear",
         "@media (prefers-reduced-motion: reduce)": ease.standard,
       },
       animationIterationCount: "infinite",
     },
   })

   const skeletonStyles = { base: styles.base }

   type SkeletonProps = useRender.ComponentProps<"div"> & {
     xstyle?: StyleXStyles
     className?: string
   }

   function Skeleton({ render, ref, style, xstyle, className, ...props }: SkeletonProps) {
     const sx = stylex.props(styles.base, xstyle)

     return useRender({
       defaultTagName: "div",
       render,
       ref,
       props: {
         "data-slot": "skeleton",
         ...sx,
         ...props,
         className: clsx(sx.className, className),
         style: { ...sx.style, ...style },
       },
     })
   }

   export { Skeleton, skeletonStyles }
   ```

4. Update the import paths to match your project setup.

## Usage

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

```tsx
const styles = stylex.create({
  line: { width: "6rem", height: space.s5, borderRadius: radius.full },
})

<Skeleton xstyle={styles.line} />
```

A skeleton has no size of its own. Give it the width and height of the content
it stands in for through `xstyle`, or through `style` when only the runtime
knows them.

A band of light sweeps across every skeleton on the page at once. The gradient
is laid out against the viewport rather than each block, so an avatar and the
lines beside it share one pass. Under `prefers-reduced-motion` the sweep becomes
a slow fade.

## Avatar

```tsx
import * as stylex from "@stylexjs/stylex"
import { Skeleton } from "@/components/ui/skeleton"
import { HStack, VStack } from "@/components/ui/stack"
import { radius, space } from "@/theme/tokens.stylex"

const styles = stylex.create({
  avatar: { width: space.s10, height: space.s10, borderRadius: radius.full },
  name: { width: "10rem", height: space.s4 },
  handle: { width: "6rem", height: space.s4 },
})

export default function SkeletonAvatar() {
  return (
    <HStack gap={4} align="center">
      <Skeleton xstyle={styles.avatar} />
      <VStack gap={2}>
        <Skeleton xstyle={styles.name} />
        <Skeleton xstyle={styles.handle} />
      </VStack>
    </HStack>
  )
}
```

## Card

```tsx
import * as stylex from "@stylexjs/stylex"
import { Card, CardContent, CardHeader } from "@/components/ui/card"
import { Skeleton } from "@/components/ui/skeleton"
import { space } from "@/theme/tokens.stylex"

const styles = stylex.create({
  card: { width: "100%", maxWidth: "20rem" },
  title: { width: "66.667%", height: space.s4 },
  description: { width: "50%", height: space.s4 },
  media: { width: "100%", aspectRatio: "16 / 9" },
})

export default function SkeletonCard() {
  return (
    <Card xstyle={styles.card}>
      <CardHeader>
        <Skeleton xstyle={styles.title} />
        <Skeleton xstyle={styles.description} />
      </CardHeader>
      <CardContent>
        <Skeleton xstyle={styles.media} />
      </CardContent>
    </Card>
  )
}
```

## Text

```tsx
import * as stylex from "@stylexjs/stylex"
import { Skeleton } from "@/components/ui/skeleton"
import { VStack } from "@/components/ui/stack"
import { space } from "@/theme/tokens.stylex"

const styles = stylex.create({
  root: { width: "100%", maxWidth: "20rem" },
  line: { width: "100%", height: space.s4 },
  lastLine: { width: "75%", height: space.s4 },
})

export default function SkeletonText() {
  return (
    <VStack gap={2} xstyle={styles.root}>
      <Skeleton xstyle={styles.line} />
      <Skeleton xstyle={styles.line} />
      <Skeleton xstyle={styles.lastLine} />
    </VStack>
  )
}
```

## Form

```tsx
import * as stylex from "@stylexjs/stylex"
import { Skeleton } from "@/components/ui/skeleton"
import { VStack } from "@/components/ui/stack"
import { space } from "@/theme/tokens.stylex"

const styles = stylex.create({
  root: { width: "100%", maxWidth: "20rem" },
  label: { width: "5rem", height: space.s4 },
  longLabel: { width: "6rem", height: space.s4 },
  control: { width: "100%", height: space.s8 },
  button: { width: "6rem", height: space.s8 },
})

export default function SkeletonForm() {
  return (
    <VStack gap={6} xstyle={styles.root}>
      <VStack gap={3}>
        <Skeleton xstyle={styles.label} />
        <Skeleton xstyle={styles.control} />
      </VStack>
      <VStack gap={3}>
        <Skeleton xstyle={styles.longLabel} />
        <Skeleton xstyle={styles.control} />
      </VStack>
      <Skeleton xstyle={styles.button} />
    </VStack>
  )
}
```

## Table

```tsx
import * as stylex from "@stylexjs/stylex"
import { Skeleton } from "@/components/ui/skeleton"
import { HStack, VStack } from "@/components/ui/stack"
import { space } from "@/theme/tokens.stylex"

const styles = stylex.create({
  root: { width: "100%", maxWidth: "24rem" },
  name: { flexGrow: 1, height: space.s4 },
  status: { width: "6rem", height: space.s4 },
  amount: { width: "5rem", height: space.s4 },
})

export default function SkeletonTable() {
  return (
    <VStack gap={2} xstyle={styles.root}>
      {Array.from({ length: 5 }).map((_, index) => (
        <HStack gap={4} key={index}>
          <Skeleton xstyle={styles.name} />
          <Skeleton xstyle={styles.status} />
          <Skeleton xstyle={styles.amount} />
        </HStack>
      ))}
    </VStack>
  )
}
```

## API reference

Pass `render` to replace a part's element or compose it with another component.

`Skeleton` renders a `<div>` and accepts every `<div>` attribute.

### Skeleton

| Prop        | Type            | Default |
| ----------- | --------------- | ------- |
| `xstyle`    | `StyleXStyles`  | —       |
| `style`     | `CSSProperties` | —       |
| `className` | `string`        | —       |

The pulse keeps running under `prefers-reduced-motion: reduce`. It is an
opacity fade with nothing moving across the block. A still grey block would
look empty rather than loading.

### skeletonStyles

The style maps behind the component. Spread them onto any element.

| Key    | What it is                             |
| ------ | -------------------------------------- |
| `base` | The muted fill, rounding and the pulse |

### Data attributes

| Attribute              | On        | Effect                          |
| ---------------------- | --------- | ------------------------------- |
| `data-slot="skeleton"` | the block | Targets the block from a parent |
