Skeleton
A placeholder that holds the shape of content while it loads: lines of text, a circle, a rectangle or a card.
Skeleton
server - 0 KBA placeholder that holds the shape of content while it loads: lines of text, a circle, a rectangle or a card.
Try it
The code
import { Skeleton } from 'topclassui/skeleton'
<div className="max-w-sm">
<Skeleton />
</div>Give the skeleton the real dimensions through className; rows is clamped from 1 to 50, and a wrong size is a layout shift.
Every state
1 stateImport from the package
import { Skeleton } from 'topclassui/skeleton'Install once:npm i topclassui@beta zod
Examples
- Most often it is a
<Suspense>fallback. Pick the shape of what is coming withvariant:text,circle,rectorcard. - The text shape is as tall as the lines it replaces at any type step, so set the type step on it as you would on the text.
- With more than one row, the last row is two thirds wide, as a paragraph ends.
- The pulse stops under reduced motion.
- Badge, Avatar, Button, Card, Input, Table and Textarea also ship a skeleton of their own exact size.
import { Skeleton } from 'topclassui/skeleton'
/** The docs previewer stage: a circle beside three lines, where shrink-0 keeps it round. */
export default function Demo() {
return (
<div className="flex max-w-sm items-center gap-4">
<Skeleton variant="circle" />
<Skeleton />
</div>
)
}
import { Skeleton } from 'topclassui/skeleton'
/** The four shapes at their own default sizes, in a column the width of a narrow card. */
export default function Demo() {
return (
<div className="flex max-w-sm flex-col gap-6">
<Skeleton />
<Skeleton variant="circle" />
<Skeleton variant="rect" />
<Skeleton variant="card" />
</div>
)
}
import { Skeleton } from 'topclassui/skeleton'
/** One row stays full width; with more than one, the last is two thirds, like a paragraph's end. */
export default function Demo() {
return (
<div className="grid max-w-2xl grid-cols-3 items-start gap-6">
<Skeleton rows={1} />
<Skeleton />
<Skeleton rows={5} />
</div>
)
}
Invoice 2041 was paidon the seventh of Octoberby bank transfer.
Invoice 2041 was paidon the seventh of Octoberby bank transfer.
import { Skeleton } from 'topclassui/skeleton'
/** Three rows beside three lines of real text that never wrap, at two type steps, so a height mismatch shows. */
export default function Demo() {
return (
<div className="flex flex-col gap-8">
<div className="grid max-w-xl grid-cols-2 items-start gap-6 text-sm">
<p className="text-body">
<span className="block truncate">Invoice 2041 was paid</span>
<span className="block truncate">on the seventh of October</span>
<span className="block truncate">by bank transfer.</span>
</p>
<Skeleton rows={3} />
</div>
<div className="grid max-w-xl grid-cols-2 items-start gap-6 text-lg">
<p className="text-body">
<span className="block truncate">Invoice 2041 was paid</span>
<span className="block truncate">on the seventh of October</span>
<span className="block truncate">by bank transfer.</span>
</p>
<Skeleton rows={3} />
</div>
</div>
)
}
import { Skeleton } from 'topclassui/skeleton'
/** Real dimensions through className, passed last: the circle's size, the rect's height and width,
* the card's width and radius, and the width and type step of two text rows. */
export default function Demo() {
return (
<div className="flex flex-col gap-6">
<Skeleton variant="circle" className="size-12" />
<Skeleton variant="rect" className="h-control-sm w-32" />
<Skeleton variant="card" className="w-64 rounded-control" />
<Skeleton rows={2} className="w-48 text-lg" />
</div>
)
}
import { Skeleton } from 'topclassui/skeleton'
/** In right to left the circle sits at the right and the short last row starts there too;
* the card below has no side to mirror. */
export default function Demo() {
return (
<div dir="rtl" lang="ar" className="flex max-w-sm flex-col gap-6">
<div className="flex items-center gap-4">
<Skeleton variant="circle" />
<Skeleton />
</div>
<Skeleton variant="card" />
</div>
)
}
Props
Keyboard and accessibility
Nothing to operate: it is not interactive on its own.
- Every shape renders
aria-hidden, so a screen reader never reads a list of empty boxes. - Mark the loading region itself, for example with
aria-busy="true"on the element the content will fill.
Right to left
Rows start at the inline start, so the short last row and a circle beside the text sit on the right in Arabic.
Server and client
Renders on the server and ships no JavaScript.
Files: index.tsx.
On the chip: server - 0 KB.