Components / Skeleton

Skeleton

Loading placeholder primitive with rect, text, and circle variants. CSS-only shimmer, respects prefers-reduced-motion.

Section: Components

Use <Skeleton /> while async content resolves. The shimmer sweep is CSS-only and disables itself when the user has prefers-reduced-motion: reduce set; in that case the bar holds a static rgba(255,255,255,0.06) tint with no animation.

Variants

rect (default)

<Skeleton width="100%" height="120px" />

text

<Skeleton variant="text" width="80%" />

circle

<Skeleton variant="circle" height="48px" />
variants.astro | astro
<!-- rect (default) -->
<Skeleton width="100%" height="120px" />

<!-- text — three lines, decreasing width -->
<Skeleton variant="text" width="90%" />
<Skeleton variant="text" width="75%" />
<Skeleton variant="text" width="55%" />

<!-- circle — avatar placeholders -->
<Skeleton variant="circle" height="32px" />
<Skeleton variant="circle" height="48px" />
<Skeleton variant="circle" height="64px" />

Composed: card placeholder

A common combination — circle avatar, two text lines, a rect body.

card-placeholder.astro | astro
<div class="card">
  <div class="card__head">
    <Skeleton variant="circle" height="40px" />
    <div class="card__lines">
      <Skeleton variant="text" width="50%" />
      <Skeleton variant="text" width="30%" />
    </div>
  </div>
  <Skeleton width="100%" height="180px" />
</div>

Without shimmer

Pass shimmer={false} for static placeholders (e.g. cached states).

static.astro | astro
<Skeleton width="100%" height="48px" shimmer={false} />

Anatomy

Property Behavior
Box The placeholder rectangle/circle/line. Sized by `width` / `height` props.
Shimmer CSS-only sweep on a linear-gradient background. Disabled under reduced-motion.
Radius `rect` → --ds-radius-md, `text` → --ds-radius-sm, `circle` → --ds-radius-full.

Accessibility

  • Each skeleton renders role="presentation" — assistive tech skips them entirely.
  • The shimmer animation respects prefers-reduced-motion: reduce automatically — no per-call opt-out needed.
  • For long async waits (>2s), pair Skeleton with a polite role="status" region carrying a real message ("Loading orders…") so SR users know what is happening.
  • Outer dimensions should match the final content; this avoids the screen-reader's focus jumping after hydration.

Do / Don't

Property Do Don't
Layout parity Match the final content shape — same number of lines, same circle sizes. Show a single rect for a complex layout — the jolt on hydration is jarring.
Duration Show skeletons after 100-200ms of latency; never below 100ms (looks like a flash). Keep skeletons on screen >2s — switch to a real loading message at that point.
Shimmer Default shimmer on; trust the prefers-reduced-motion media query to silence it. Force `shimmer={false}` for everyone — most users benefit from the activity cue.
Cached states Use `shimmer={false}` for content that came from cache (no real wait). Use Skeleton for placeholders that will never resolve — render an empty state instead.
Above the fold Render the same outer dimensions as the resolved content — avoids cumulative layout shift. Animate height during load — destroys CLS scores.

Props

PropTypeDefaultNotes
widthstring (CSS length)100%Ignored on circle when height is set.
heightstring (CSS length)1remForced to 1em for variant="text".
variant"rect" | "text" | "circle""rect"Picks the corner radius.
shimmerbooleantrueAuto-disabled under prefers-reduced-motion: reduce.
classstring""Appended after default classes.

Tokens consumed

  • --ds-radius-md — rect variant corners
  • --ds-radius-sm — text variant corners
  • --ds-radius-full — circle variant

Source

src/components/ui/Skeleton.astro