Skeleton

Reserve space while content is still loading.

loadingstatus
Preview
Skeleton preview

When to use

  • Loading states for content whose shape is known, like a profile or comment.
  • Swapping a placeholder into real content with a crossfade and height spring.

When not to use

  • Use progress when you can report a percentage.
  • Use empty-state when loading finished and there is nothing to show.
  • Use text-shimmer for an AI thinking or status line.

shadcn CLI

Also installs (1)

  • manicat-foundation
Manual install(+)
registry.jsonjson
{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "skeleton",
  "type": "registry:ui",
  "title": "Skeleton",
  "description": "Reserve space while content is still loading.",
  "categories": [
    "feedback"
  ],
  "dependencies": [
    "motion"
  ],
  "registryDependencies": [
    "https://manicat.viraza.net/r/manicat-foundation.json"
  ],
  "files": [
    {
      "path": "registry/components/skeleton/skeleton.tsx",
      "type": "registry:component",
      "target": "@components/manicat/skeleton/skeleton.tsx"
    },
    {
      "path": "registry/components/skeleton/skeleton.module.css",
      "type": "registry:component",
      "target": "@components/manicat/skeleton/skeleton.module.css"
    }
  ],
  "docs": "Docs and live preview: https://uiarc.dev/components/skeleton",
  "meta": {
    "tier": "free",
    "kind": "component",
    "docs": "https://manicat.viraza.net/components/skeleton",
    "markdown": "https://uiarc.dev/components/skeleton/markdown",
    "tags": [
      "loading",
      "status"
    ]
  }
}

Usage

Install the component, then copy this into your app. Change the content and the props to fit your screen.

example.tsxtsx
"use client";

import { Skeleton } from "@/components/manicat/skeleton/skeleton";

export function SkeletonExample() {
  return (
    <div className="grid w-full max-w-md gap-3">
      <Skeleton avatar lines={3} label="Loading profile" />
      <Skeleton lines={2} />
    </div>
  );
}

Examples

Real screens rather than isolated props. Switch to Code to copy a whole example into your project.

Loading a profile The placeholder keeps the final layout, then crossfades into the real content.

Loading a profile The placeholder keeps the final layout, then crossfades into the real content. preview

Accessibility

  • The placeholder is role="status" with aria-busy and an aria-label; its shapes are aria-hidden.
  • With children, the wrapper sets aria-busy while loading.

Motion

  • Blocks pulse in a staggered wave, each a beat after the one above.
  • On load the placeholder fades out, content rises 4px into place, and the height springs from placeholder to content.
  • Reduced motion stops the pulse and swaps with short fades and no height animation.

Responsive behavior

  • Lines are percentages of the container width, so the placeholder scales with its slot.
  • It only draws text lines and an avatar; build custom shapes for grids or media.

Performance

  • The pulse is a CSS animation that stops under reduced motion.
  • A ResizeObserver springs the height from placeholder to content; avoid hundreds of skeletons at once.

Notes for AI

  • Use while fetching content whose shape is known. Use progress when you can report a percentage, and empty-state when there is nothing to show.
  • Wrap the real content as children and drive loading to get the crossfade; without children it renders only the placeholder.