# Skeleton

> A React skeleton loader shaped like the content on its way: a shimmering block, text lines and presets for cards, lists and tables.

- Type: React component (Feedback)
- Access: Free (the source is open to everyone)
- Version: 1.0.0
- Install: `npx wingo-ui@latest add skeleton`
- Docs: https://wingo-ui.com/components/skeleton

## Interaction

Nothing to press: blocks shimmer or pulse while data loads, and the real content fades in the moment loading turns off. Tap Reload in the playground to replay a 1.4s request and watch the cards swap without the layout moving. Wrap real content in a Skeleton and the block takes its exact size and corners; the wrapped content is inert while loading, so Tab skips it until it arrives. On a phone the table preset turns into one card per row. Screen readers hear each preset's label once, never every block, and reduced motion keeps every block still.

## Installation

```bash
npx wingo-ui@latest add skeleton
```

The CLI adds the files, their dependencies and a version record for updates.

With shadcn: `npx shadcn@latest add https://wingo-ui.com/r/skeleton.json`

Files:

- `components/ui/skeleton.tsx`

npm dependencies: `npm i motion`

Wingo UI dependencies (installed automatically):

- [UIProvider](https://wingo-ui.com/components/ui-config) ([Markdown](https://wingo-ui.com/components/ui-config.md))
- [Motion presets](https://wingo-ui.com/components/motion) ([Markdown](https://wingo-ui.com/components/motion.md))

## Usage

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

type Listing = { title: string; city: string; photo: string }

export function ListingCard({ listing }: { listing?: Listing }) {
  if (!listing) return <SkeletonCard label="Loading the listing" />
  return (
    <article className="flex flex-col gap-3">
      <img src={listing.photo} alt="" className="aspect-[16/10] w-full rounded-[10px] object-cover" />
      <h3 className="text-[15px] font-semibold">{listing.title}</h3>
      <p className="text-[13px] text-muted-foreground">{listing.city}</p>
    </article>
  )
}

export function Seller({ loading }: { loading: boolean }) {
  return (
    <div className="flex items-center gap-3">
      <Skeleton shape="circle" loading={loading}>
        <img src="/ana-pop.jpg" alt="Emma Carter" className="size-10 rounded-full" />
      </Skeleton>
      <Skeleton shape="line" loading={loading}>
        <p className="text-sm font-medium">Emma Carter</p>
      </Skeleton>
    </div>
  )
}
```

## Props

| Prop | Type | Default | Description |
| --- | --- | --- | --- |
| `shape?` | `"rect" \| "circle" \| "line"` | `"rect"` | rect: a block with radius (full width, 96px tall until you size it). circle: a round block, 40px unless width says otherwise. line: a 14px text line. |
| `radius?` | `"none" \| "sm" \| "md" \| "lg" \| "full"` | `"md"` | Corners of a rect: 0 / 6 / 10 / 16px or a pill. Circles and lines are always round. A rect wrapping real content copies the corners of what it wraps when that has any (a button, a rounded photo). |
| `animation?` | `"shimmer" \| "pulse" \| "none"` | `"shimmer"` | shimmer: a soft highlight sweeps across. pulse: the block fades in and out. none: static. Under reduced motion every block stays still. |
| `color?` | `string` | `""` | Any CSS color for the block. Leave empty for the 5% foreground tint, which works on every surface in both themes. Set it on colored surfaces, e.g. a white tint on a brand banner. |
| `highlight?` | `string` | `""` | Any CSS color for the shimmer band. Leave empty for a 7% foreground tint. |
| `speed?` | `number` | `1` | Playback multiplier for shimmer and pulse: 0.5 is half speed, 2 twice as fast. One shimmer sweep takes 1.6s at 1, a pulse 2s. Clamped to 0.25..4. |
| `loading?` | `boolean` | `true` | false renders the children instead, fading them in. Content that is already there on the first render (the server's too) shows without a fade. |
| `children?` | `ReactNode` |  | The real content. While loading it sits invisible and inert inside the block, so the block takes its exact size (a rect its corners too, a line draws a bar 70% as tall as the text box, like the presets); once loaded it shows in a wrapper that fades in. className lands on both, so pass layout classes like w-full or flex-1 once. |
| `width?` | `number \| string` | `""` | px as a number (or a number-like string such as "120"), any other CSS length as a string. rect and line fill the row by default; a circle takes its size from width, or from height when width is missing. |
| `height?` | `number \| string` | `""` | px as a number (or a number-like string), any other CSS length as a string. A rect is 96px tall by default, or follows an aspect-* class. Ignored by circles, which stay round. |
| `className?` | `string` |  | Extra classes for the block, merged last: size it with h-4 w-32, size-12, aspect-video. |

## Parts

### SkeletonText (component)

A column of lines: 3 by default, the last at 60%. size sm / md / lg makes 10 / 14 / 18px lines (text-xs, text-sm, text-[15px]). lines={0} renders nothing. Props: { lines?: number; lastLineWidth?: number | string; size?: "sm" | "md" | "lg"; animation; speed; color; highlight; classNames?: { line } }.

### SkeletonCard (component)

A media card: a 16:10 block on top (media, true; mediaAspect takes any CSS aspect ratio, "1" for square thumbnails), a title and meta lines (lines, 2), a price line and a round action (footer, true). role="status" with a hidden label ("Loading"). Every preset also takes animation, speed, color and highlight. Props: { media?: boolean; mediaAspect?: string; lines?: number; footer?: boolean; radius?; label?: string; classNames?: { media, body, footer } }.

### SkeletonList (component)

List group rows (56px, px-4): a 40px avatar, a title and a subtitle, a button-sized action, hairlines between rows. 4 rows by default. classNames.avatar="rounded-[10px]" makes square thumbnails. Props: { rows?: number; avatar?: boolean; action?: boolean; dividers?: boolean; label?: string; classNames?: { row, avatar, action } }.

### SkeletonTable (component)

A table-shaped grid from 768px up (a header and 48px rows, the first column wider), one card per row below: a title line and two label / value pairs. Pure CSS, right on the first paint. 5 rows, 4 columns. Props: { rows?: number; columns?: number; header?: boolean; label?: string; classNames?: { header, row, card } }.

### SkeletonProfile (component)

A profile header: a cover the avatar overlaps through a round cut-out, name and meta lines, and a row of stats (3). avatarSize is in px (80). Props: { cover?: boolean; stats?: number; avatarSize?: number; radius?; label?: string; classNames?: { cover, avatar, stats } }.

## Examples

- **Primitives**: A circle and two lines for a list row, then a rect in each radius.
- **Text**: SkeletonText with 1, 3 and 5 lines at lg, md and sm. The last line stops short, like real text.
- **List, then the real rows**: SkeletonList in a list group swaps to the real rows after a fake 1.4s request. The rows share its rhythm, so nothing moves.
- **Table on every screen**: A table-shaped grid from 768px up, one card per row on phones. Pure CSS, right on the first paint.
- **Profile**: The avatar overlaps the cover through a round cut-out, so the two tints never stack into a darker patch.
- **Auto-size**: Wrap the real content: while loading it sits invisible inside the block, which takes its exact size and corners.
- **Shimmer, pulse, none**: A sweeping highlight, a slow fade, or a still block. Reduced motion always gets the still one.
- **On a colored surface**: color and highlight take any CSS color, here white tints on a brand banner.

## See also

- [Spinner](https://wingo-ui.com/components/spinner) ([Markdown](https://wingo-ui.com/components/spinner.md))
- [Avatar](https://wingo-ui.com/components/avatar) ([Markdown](https://wingo-ui.com/components/avatar.md))
- [Motion presets](https://wingo-ui.com/components/motion) ([Markdown](https://wingo-ui.com/components/motion.md))

---

Canonical: https://wingo-ui.com/components/skeleton
More components: https://wingo-ui.com/components · Index for agents: https://wingo-ui.com/llms.txt
