Gesso
Feedback

Skeleton

A pulsing placeholder block that holds the shape of content while it loads.

Skeleton is a loading placeholder, a block on bg-surface-sunken that pulses. You size it and shape it with classes to match the content that will replace it, so the layout does not jump when the data arrives.

Usage

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

<div className="flex items-center gap-4">
  <Skeleton className="size-12 rounded-full" />
  <div className="grid flex-1 gap-2">
    <Skeleton className="h-4 w-3/4" />
    <Skeleton className="h-4 w-1/2" />
  </div>
</div>

Match the content's shape

Skeleton has no size of its own. It is a <div> with rounded-md, so give each block the height and width of what it stands for, like h-4 w-3/4 for a line of text and size-12 rounded-full for an avatar. Several blocks side by side read as a row, and the placeholders can keep the same grid or flex layout as the loaded content. The pulse is a Tailwind animate-pulse, and motion-reduce:animate-none stops it under reduced motion, so the block then sits still.

Swap on load

Render the skeleton while a request is pending and the real content when it resolves. Put aria-busy on the region while it loads, and set it back when it is done. Click Reload to run it.

Accessibility

  • A plain skeleton is a <div> with no role, name, or text, so it reads as nothing. Give one skeleton per loading region a label, like "Loading messages", and it becomes a role="status" with that text visually hidden, so a screen reader hears it. Leave the label off the rest, or each would announce.
  • aria-busy="true" on the region that is loading is the other half, and support for it varies between screen readers, so the status label is the safer of the two.
  • The pulse is decorative, and under prefers-reduced-motion it stops.
  • Forced colors replace the background, so the block may not draw at all there.
  • Skeleton is not focusable and takes no input, so nothing in it can be reached while it stands in for a control.

When to use something else

  • Progress when you know how far along the work is, as a value toward a total.
  • A Button's loading when the wait is for an action a person started, and the button itself is the right place to show it.
  • Empty state once loading finishes and there is nothing to show. Do not leave a skeleton up for a result that is empty.
  • Toaster for the result of a background action. A skeleton marks a region that is not ready, not an event.

Install

shadcn add @gesso/skeleton

On this page