Skeleton auto
Free · MITSkeletonSwap traces its children's text lines, controls and avatars (<img> or data-skeleton elements) into shimmer blocks with one shared sweep, then cross-fades to the real content; includes line, circle and block primitives.
Installation
$ npx shadcn@latest add https://goodui.dev/r/skeleton-auto.jsonThe file lands in components/goodui/skeleton-auto.tsx and is yours to edit. With the @goodui registry added, npx shadcn@latest add @goodui/skeleton-auto works too.
Usage
import { Avatar, AvatarFallback } from "@/components/ui/avatar"
import { SkeletonSwap } from "@/components/goodui/skeleton-auto"
const placeholder = { name: "Maya Chen", email: "maya.chen@hey.com", notes: "Prefers Priya for color. Patch test before every appointment." }
export function ClientCard({ client, loading }: { client?: typeof placeholder; loading: boolean }) {
const c = client ?? placeholder
return (
<SkeletonSwap loading={loading} className="rounded-xl border bg-card p-5">
<div className="flex items-center gap-3">
<Avatar className="size-12" data-skeleton>
<AvatarFallback>{c.name.split(" ").map((n) => n[0]).join("")}</AvatarFallback>
</Avatar>
<div className="min-w-0">
<div className="font-semibold">{c.name}</div>
<div className="text-sm text-muted-foreground">{c.email}</div>
</div>
</div>
<p className="mt-4 text-sm text-muted-foreground">{c.notes}</p>
</SkeletonSwap>
)
}When to use
- Cards, lists and profiles that load data, where you want a skeleton shaped exactly like the result without drawing one by hand.
- Layouts that change often: the skeleton follows the markup, so it never drifts out of date.
- Hand-made skeletons too: Skeleton, SkeletonLine and SkeletonCircle share the same shimmer.
- Short waits in a button or input: use spinners.
- Operations with known progress: use progress.
- Multi-stage tasks the user should follow step by step: use multi-step-loader.
| If you need | Use |
|---|---|
| Skeleton shaped like the real content | <SkeletonSwap loading> with placeholder data as children |
| Full control over the shapes | fallback={…} built from SkeletonLine / SkeletonCircle / Skeleton |
| Avatar or chart as one solid block | data-skeleton on that element |
| Leave a part out (badges, decorations) | data-skeleton-ignore on that element |
| Standalone placeholder block | <Skeleton className="h-24 w-full rounded-xl" /> |
Examples
Hand-made fallback
<SkeletonSwap
loading={loading}
className="rounded-xl border bg-card p-4"
fallback={
<div className="flex items-center gap-3">
<SkeletonCircle size={36} />
<div className="flex flex-1 flex-col gap-2">
<SkeletonLine width="45%" />
<SkeletonLine width="70%" className="h-2.5" />
</div>
</div>
}
>
<LocationSummary location={location} />
</SkeletonSwap>Primitives on their own
<div className="flex flex-col gap-2">
<Skeleton className="h-32 w-full rounded-xl" />
<SkeletonLine width="60%" />
<SkeletonLine width={120} />
</div>API reference
Skeleton
Also accepts every <div> attribute.
SkeletonLine
| Prop | Type | Default |
|---|---|---|
| width CSS width, e.g. "60%" or 120. | number | string | "100%" |
Also accepts every <div> attribute.
SkeletonCircle
| Prop | Type | Default |
|---|---|---|
| size Diameter in px. | number | 40 |
Also accepts every <div> attribute.
SkeletonSwap
| Prop | Type | Default |
|---|---|---|
| loading* While true, the children are hidden and the skeleton shows; set it to false to cross-fade to the content. | boolean | — |
| children* The real content. While loading, render it with placeholder data: it stays invisible and its layout is traced into skeleton blocks. Mark parts with data-skeleton (one solid block) or data-skeleton-ignore (skip). | React.ReactNode | — |
| fallback Hand-made skeleton instead of the traced one. | React.ReactNode | — |
| loadingLabel Announced to screen readers while loading. | string | "Loading…" |
Also accepts every <div> attribute.
Accessibility
- While loading, the root has aria-busy and an sr-only role="status" says loadingLabel (default "Loading…"; pass a translation).
- The placeholder content is inert and aria-hidden while loading, so nobody can focus or read the fake data; the skeleton layer is aria-hidden too.
Motion
- One highlight band sweeps across every block every 1.7 s; each block offsets it by its own x position, so it reads as a single sweep over the whole layout.
- Switching loading cross-fades the skeleton and the content over 350 ms (ease [0.4, 0, 0.2, 1]).
- Reduced motion: the sweep is hidden, blocks stay static and the cross-fade shortens to 150 ms.
- Tracing walks the content's DOM once per change: one block per wrapped text line (via Range rects) and one per image, svg, video, canvas, input, textarea, select, button, picture, iframe, progress, meter or data-skeleton element.
- It re-traces on resize (ResizeObserver) and DOM mutations (MutationObserver), batched to one animation frame, and only while loading is true.
Notes for AI
Coding agents read these rules through MCP and llms.txt, or paste this page with Copy page.
- Render the real component with realistic placeholder data while loading; empty strings produce no text lines and therefore no skeleton.
- Mark avatars, charts and thumbnails with data-skeleton so they become one block; a text-only avatar fallback would otherwise trace as a short text line.
- className goes on the root (it keeps your padding and border); the skeleton is positioned against its padding box.
- With fallback, nothing is traced; the fallback sits in the same grid cell as the content.
- Elements with display: none or visibility: hidden are skipped automatically.
FAQ
Does it work on the server?
Blocks are measured in the browser in a layout effect, so the server HTML has the invisible content (keeping the right size) and the blocks appear before the first paint after hydration.
Why are my text bars shorter than the line?
Each bar is 62% of the line height (at least 6px) and centered on it, like real glyphs, and is clipped to its parent's box.
Related
Six CSS-only loaders (ring, dots, bars, pulse, orbit, typing) sized in px or presets and painted with currentColor.
Linear progress in four variants: spring-filled determinate, indeterminate double sweep, moving stripes and segmented steps.
Card or overlay that ticks through steps like "Reserving slot…" with spinners, drawn checkmarks and a filling rail, ending on an animated success state.
Eight surfaces (elevated, glass, gradient border, neo, inset…) with shadow, padding, radius and accent stripes, plus hover layers (lift, 3D tilt, pointer spotlight, glow, scale) and a looping border beam.