1
ComponentsFeedback

Skeleton

Content-shaped loading placeholders with a shared shimmer and stable layout dimensions.

Purpose

Use Skeleton to reserve the approximate shape of content while a layout is loading and the final value is not yet available.

Loading layout

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

export function Example() {
  return <Skeleton className="h-8 w-48" />;
}

Installation

With the Siteplane registry alias configured, add the component and import the installed source from your app.

CLI

pnpm dlx shadcn@4.16.1 add @siteplane/skeleton

Import

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

Need the machine-readable source metadata? View registry JSON.

Usage

Skeleton reserves space for loading content. Match the final layout instead of showing generic bars everywhere.

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

export function Example() {
  return <Skeleton className="h-8 w-48" />;
}

Examples

Shapes come from className; loading layouts add aria-busy containers and a real skeleton-to-content switch.

Shapes

Lines, blocks, avatars and button placeholders are the same primitive with different className shapes.

{/* Lines */}
<Skeleton className="h-4 w-3/4" />
<Skeleton className="h-4 w-1/2" />

{/* Block */}
<Skeleton className="h-24 w-full rounded-lg" />

{/* Avatar and button placeholder */}
<Skeleton className="size-10 rounded-full" />
<Skeleton className="h-7 w-19 sm:h-6 sm:w-17" />

Card loading

aria-busy on the container announces the loading state; the skeletons themselves stay hidden.

<div aria-busy="true" aria-label="Customer card is loading" className="grid gap-3 rounded-lg border p-3">
  <div className="flex items-center gap-3">
    <Skeleton aria-hidden="true" className="size-9 rounded-full" />
    <div className="grid min-w-0 flex-1 gap-1.5">
      <Skeleton aria-hidden="true" className="h-4 w-2/3" />
      <Skeleton aria-hidden="true" className="h-3 w-1/2" />
    </div>
  </div>
  <Skeleton aria-hidden="true" className="h-16 w-full rounded-md" />
</div>

Table row

<div aria-busy="true" aria-label="Table row is loading" className="flex items-center gap-3 rounded-lg border p-3">
  <Skeleton aria-hidden="true" className="size-8 rounded-full" />
  <div className="grid min-w-0 flex-1 gap-1.5">
    <Skeleton aria-hidden="true" className="h-3.5 w-3/4" />
    <Skeleton aria-hidden="true" className="h-3 w-1/2" />
  </div>
  <Skeleton aria-hidden="true" className="h-6 w-14" />
</div>

User card loading

Cards switch from skeleton to loaded content after a delay; Reload restarts the cycle.

function UserCard({ delay, user }: { delay: number; user: ExampleUser }) {
  const [isLoaded, setIsLoaded] = useState(false);

  useEffect(() => {
    const timer = setTimeout(() => setIsLoaded(true), delay);
    return () => clearTimeout(timer);
  }, [delay]);

  if (!isLoaded) {
    return (
      <div aria-busy="true" className="flex items-center gap-4">
        <Skeleton aria-hidden="true" className="size-10 rounded-full" />
        <div className="flex flex-1 flex-col gap-1">
          <Skeleton aria-hidden="true" className="h-4 max-w-54" />
          <Skeleton aria-hidden="true" className="h-4 w-1/2" />
        </div>
        <Skeleton aria-hidden="true" className="h-7 w-19" />
      </div>
    );
  }

  return (
    <div className="flex items-center gap-4">
      <Avatar className="size-10">
        <AvatarFallback>{user.fallback}</AvatarFallback>
      </Avatar>
      <div className="flex min-w-0 flex-1 flex-col gap-1">
        <h4 className="font-medium text-sm">{user.name}</h4>
        <span className="text-muted-foreground text-xs">{user.role}</span>
      </div>
      <Button>
        <UserRoundPlusIcon />
        Follow
      </Button>
    </div>
  );
}

API Reference

The reference lists the source-owned exports and props that are easy to miss in visual examples. Inherited Base UI props remain available unless the wrapper narrows them.

  • Skeleton accepts standard div props. Set its dimensions and shape with existing layout and radius utilities.
  • The primitive owns data-slot="skeleton" and the shared shimmer animation; it does not introduce content-specific variants.

Motion

Component motion uses the shared Siteplane motion contract. See the global motion guide for provider setup, tokens and reduced-motion behavior.

  • Skeleton uses the central animate-skeleton shimmer. Both the provider override and the operating-system reduced-motion preference stop it through the shared siteplane-skeleton-motion contract.
  • Do not add a second animation, fixed background attachment or component-local timing values.

Accessibility

  • Keep placeholders hidden from the accessibility tree when they carry no meaningful status.
  • Announce loading on the surrounding region when users need that information; do not expose fake text or fake controls to screen readers.
  • Preserve layout without making the placeholder look interactive.

Implementation Guidance

  • Shape Skeleton with normal layout utilities instead of adding parallel variants or wrappers.
  • Use Spinner for an indeterminate operation indicator and Skeleton for content-shaped loading.
  • Remove the placeholder when real content arrives rather than layering both states.

On This Page