Skeleton
A loading placeholder that swaps between a pulsing/shimmering shape and its children, based on a
loading flag.
Installation
npm install @sipe-team/skeleton
import '@sipe-team/skeleton/styles.css';
Usage
Skeleton needs an explicit loading value plus a width/height — without them, the placeholder
(rectangular by default) has no intrinsic size and renders as an empty box.
Examples
Loading vs. loaded
While loading is true, children are not rendered — only the placeholder shape is shown. Once
loading is false, Skeleton renders children in its place.
Variants
rectangular (default), circle, text, and rounded control the placeholder's border radius —
circle also forces a 1:1 aspect ratio, and text defaults its height to 1em when no height is
given.
Multiple text lines
lines (only meaningful with variant="text") renders that many stacked line placeholders instead
of one; the last line is narrower (75% width) to read like a paragraph's ragged end.
Pulse and shimmer
pulse (default true) fades the placeholder in and out. shimmer (default false) adds a
lighter sweep across it; the two can be combined.
asChild
Merges the placeholder styling onto the element you provide instead of wrapping it in a div, and —
unlike the default mode — renders children while loading is true. Use it to keep a real
element's exact box shaped correctly under the shimmer.
Anatomy
import { Skeleton } from '@sipe-team/skeleton';
export default () => (
<Skeleton loading={isLoading} variant="rounded" width={200} height={100}>
<RealContent />
</Skeleton>
);
When loading is true (and it is not the multi-line text case below), renders a single <div>
(or the child element when asChild is set). When loading is false, renders children directly
with no wrapper at all. When variant="text" and lines > 1, always renders a wrapping <div> of
lines individual line placeholders, ignoring asChild.
API Reference
Forwards its ref to the rendered placeholder element — except when loading is false, where
children is returned as-is and there is no ref target.
| Prop | Type | Default | Description |
|---|---|---|---|
loading | boolean | — (required) | Shows the placeholder when true; renders children as-is when false. |
children | ReactNode | — | Content to render once loading is false (and, with asChild, while loading too). |
variant | 'rectangular' | 'circle' | 'text' | 'rounded' | 'rectangular' | Placeholder shape. |
width | number | string | — | Placeholder width. Numbers are treated as pixels. |
height | number | string | — | Placeholder height. Numbers are treated as pixels. Defaults to 1em when variant="text". |
lines | number | 1 | Number of stacked line placeholders, used when variant="text" and lines > 1. |
pulse | boolean | true | Fade in/out animation. |
shimmer | boolean | false | Adds a lighter sweep animation, layered with pulse. |
asChild | boolean | false | Merge placeholder styling onto the child instead of rendering a div. Ignored when variant="text" and lines > 1. |
Also accepts every ComponentProps<'div'> (className, style, onClick, …), forwarded to the
rendered element.
Accessibility
- The single-placeholder render path (everything except multi-line
text) setsaria-busy="true"and, unless a customaria-labelis passed, a defaultaria-label="Loading content"whileloadingistrue. - The multi-line
textcase (variant="text"withlines > 1) does not setaria-busyor a defaultaria-labelon its wrapper — pass an explicitaria-labelif it needs to be announced.
Known limitations
- Without an explicit
width/height(or, fortext, at least awidth), therectangular,circle, androundedvariants have no intrinsic size and render as an empty box unless their parent constrains it. - When
loadingisfalse,Skeletonreturnschildrendirectly —className,style,asChild, andrefare all dropped in that render.Skeletoncannot be used as a stable wrapper element across the loading/loaded transition. asChildis ignored whenvariant="text"andlines > 1; that case always renders a plain<div>of line placeholders regardless ofasChild.