Skeleton
The placeholder a marketing surface holds while its content is in flight.
Hold the shape, not a spinner
A skeleton is worth reaching for when the layout already knows what is coming and only the content is late. It keeps the box that the content will land in, so nothing around it moves when the swap happens. Where the shape is not known ahead of time, do not guess one: leave the space empty.
import { Skeleton, SkeletonGroup } from "@rogo-technologies/brand/skeleton";
<SkeletonGroup className="flex flex-col gap-4" label="Loading the featured story">
<Skeleton className="aspect-[16/10] w-full rounded-xl" />
<Skeleton variant="text" className="type-marketing-body-medium-md" width="60%" />
</SkeletonGroup>;The site's own case is the header: the Resources panel's feature card is the newest newsroom post, fetched by the header after the page rather than rendered into it, so the pages that hold no CMS content stay static. The panel holds the card's column at these proportions until it arrives.
Props
| Prop | Type | Default | What it does |
|---|---|---|---|
variant | "block" | "text" | "block" | block takes its height from you; text is 1em tall. |
tone | "light" | "dark" | "light" | Which ground it sits on. |
width | number | string | not set | CSS width; a number is pixels. Tailwind width classes work too. |
SkeletonGroup takes a label (default "Loading") and any div props.
Sizing
variant="text" is 1em tall, so it takes its height from the type it stands in for: give it the
same type-marketing-* utility as the line it replaces and the placeholder and the copy occupy the
same band. A block needs a height from you, usually an aspect-* or a h-*.
It stays square
Like every other marketing surface. Add a radius only where the thing being replaced carries one,
such as the nav panel's picture (rounded-xl) or a pill. A placeholder that is a different shape
from its content reads as the layout breaking rather than as content arriving.
Announced once
Skeleton is aria-hidden: there is nothing in it to read. SkeletonGroup is the live region that
says the thing is loading, so a card standing in for a picture and two lines announces once rather
than three times. Wrap every loading state in one, including a single box.
Pass your own role or aria-label to a Skeleton only when that one placeholder genuinely is the
whole loading state and you are describing it yourself; the component then leaves it in the
accessibility tree.
Motion
One animate-pulse, dropped entirely under prefers-reduced-motion. No shimmer sweep: the
marketing surfaces are quiet, and a travelling highlight on a page of stills draws the eye to the
one thing that has not arrived.