Skeleton
A placeholder that reserves the shape of what is loading.
@luc/uiExamples
A placeholder that keeps the shape it reserved
Press the button: the same rows arrive where the placeholder was, and nothing on the card moves. That is the reason to reach for a skeleton instead of a spinner.
Every line is drawn at the height of the line it stands in for.
{loaded ? <Text>{row.name}</Text> : <Skeleton variant="text" className="h-3.5 w-full" />}The three variants
`text` is a line, `block` is a surface, `circle` is a person or a mark.
text
block
circle
<Skeleton variant="text" />
<Skeleton className="h-16" />
<Skeleton variant="circle" className="size-10" />A list that reserves its rows
A table's placeholder is a grid, not a paragraph: the columns have to line up with the columns that are coming.
{rows.map((row) => (
<Skeleton key={row.id} variant="text" className="h-3.5" />
))}Shimmer, on and off
One shimmer per view is feedback; a dozen is noise. The static surface is the honest choice for everything else.
shimmer · the page's main wait
no shimmer · a quiet region
<Skeleton className="h-12" />
<Skeleton className="h-12" shimmer={false} />A page region, not a page
A region that is loading keeps its own frame: the panel, the heading and the chart body are all already there.
<Skeleton variant="text" className="h-4 w-40" />
<SkeletonText lines={3} />
<Skeleton className="h-24" />Usage
import { Skeleton, SkeletonText } from "@luc/ui";<SkeletonText lines={3} />
<Skeleton variant="circle" className="size-9" />A `block` reserves a surface, a `circle` an avatar, and text lines come from `SkeletonText`.
Package setup — peer requirements, the stylesheet and the provider — is on installation.
API
Read from the published TypeScript API, so a prop that is not listed here does not exist and a default that is listed is the one the compiler sees.
Two components: the placeholder itself (`variant` and `shimmer`) and `SkeletonText`, the multi-line helper that varies its line widths so a block reads as prose.
| Export | Kind | Signature |
|---|---|---|
Skeleton | 2 documented props | ({ className, variant, shimmer, ...props }: SkeletonProps) => import("@types/react").JSX.Element |
SkeletonText | 3 documented props | ({ lines, widths, className, ...props }: SkeletonTextProps) => import("@types/react").JSX.Element |
Skeleton props
Type: SkeletonProps
Values the compiler reads from the declaration.
| Prop | Type | Default | Notes |
|---|---|---|---|
shimmer? | boolean | undefined | — | Sweep a highlight across the placeholder. Default `true`. |
variant? | "circle" | "text" | "block" | undefined | — | `text` is a single line, `block` a surface, `circle` an avatar. |
SkeletonText props
Type: SkeletonTextProps
Values the compiler reads from the declaration.
| Prop | Type | Default | Notes |
|---|---|---|---|
lines? | number | undefined | — | |
shimmer? | boolean | undefined | — | Sweep a highlight across the placeholder. Default `true`. |
widths? | string[] | undefined | — | Vary line widths so the block reads as prose, not a table. |
Guidelines
Do
- Reserve the shape of what is coming: the same number of lines, the same row height, the same avatar size.
- Use `SkeletonText` for prose and hand-written widths for anything with a rhythm of its own.
- Keep the placeholder in the loaded content's own layout, so nothing moves when the data lands.
Don't
- Do not let a skeleton be taller or shorter than the content it replaces — that is a layout shift with extra steps.
- Do not shimmer a dozen placeholders: the page's one main wait is worth the movement, the rest is noise.
- Do not use a skeleton for an action's wait inside a button; that is a spinner.
Accessibility
A skeleton is `aria-hidden` on purpose: it is a picture of content, not content. That makes the live relationship the caller's job — `aria-busy` on the loading region is what tells a screen reader that what it can read there is not final — and the shimmer is `motion-safe`, so a reader who asked for reduced motion gets a static placeholder with the same geometry.
Family
Feedback
What the product says when something happened, is happening, or did not.
10 documented entries in this family. Open the family index
Related