Skeleton
Placeholders measured in lines, not pixels, so loaded text replaces them without moving anything.
Installation
npx shadcn@latest add @quireui/skeletonUsage
{loading ? <SkeletonText className="w-32 font-medium" /> : <p className="font-medium">{name}</p>}
{loading ? <Skeleton className="size-10 rounded-full" /> : <Avatar />}Put the placeholder exactly where the real thing goes, with the same classes, and swap one for the other.
Measured in lines
SkeletonText is one line-height of the text around it, with the bar centred where the letters sit. In a text-xl heading it is a heading line; in text-sm it is a small line. When the text arrives it takes exactly that space, so nothing below it moves, at any font size and any density.
lines stacks several, with the last one shorter, like the end of a paragraph.
Shapes
Skeleton is for everything that is not text. Give it the size and radius of the real element: size-10 rounded-full for an avatar, the badge height for a badge. It uses the control radius by default.
In a table
Table rows have a fixed height, so skeleton cells never change the row. Vary the widths down a column, so it reads like names rather than stripes.
Accessibility
Placeholders are hidden from screen readers. Mark the region that is loading with aria-busy, so it is skipped until the content is there.
Skeletons take no focus and have no keys. Keep the controls around them usable while content loads where you can, and disable only what depends on the data.
Motion
Placeholders hold their space at once but fade in only after 150ms, so a fast load never flashes them. While they stay, a faint light sweeps across every 2 seconds or so, to say the work is still going. The sweep stops under reduced motion; the placeholders stay.
Skeleton or something else?
- Work you can measure, like an upload: a progress toast.
- A button that is busy: the Button's
loadingstate.
API reference
Skeleton
A loading placeholder for anything that is not text: an avatar, a badge, a chart. Size it with the same classes as the real thing, e.g. size-8 rounded-full, so nothing moves when it loads.
Also takes every <div> attribute. Style it with [data-slot="skeleton"].
SkeletonText
Placeholder lines measured in lines, not pixels: each is exactly one line-height of the text around it, with the bar centred where the letters sit. Put it where the text will go, in the same font size, and the text replaces it without moving anything.
Prop
Type
Also takes every <div> attribute. Style it with [data-slot="skeleton-text"].