Skeleton

Holds the shape of content that has not arrived yet.

import { Skeleton } from "@usefragments/ui";Source

Preview

<Skeleton width={200} height={20} />

Usage

Use when

  • Content is still being fetched
  • You want to stop the layout jumping when it lands
  • Making a wait feel shorter than it is

Avoid when

  • Waits under about 300ms
  • A spinner says it better
  • Work the user cannot see anyway

Implementation notes

  • Match the shape and size of the real content
  • Prefer the text, heading, avatar, control and row shapes over hand-rolled boxes
  • Give Skeleton.Text an ancestor with a real width — percentage lines collapse without one
  • A few big shapes beat many small ones
  • Reduced motion holds every bar still on its own; there is no prop for it
  • Skeleton and Skeleton.Text forward id, aria-*, data-* and handlers

Installation

pnpm add @usefragments/ui

The npm package stays managed by your package manager. Import styles once from @usefragments/ui/styles. See the installation guide for framework and theme setup.

Anatomy

Import Skeleton once, then compose its parts with dot notation.

  • Skeleton.Text

Examples

Text Lines

Stacked lines that fill the width they are given.

<div style={{ width: 260 }}>
  <Skeleton.Text lines={3} />
</div>

Semantic Shapes

A 12px heading bar over 10px text bars.

<div
  style={{
    width: 260,
    display: 'flex',
    flexDirection: 'column',
    gap: 'var(--fui-raw-space-12)',

Avatar Skeleton

Squares at the indicator radius, at Avatar's xs, sm and md sizes.

<div style={{ display: 'flex', gap: 'var(--fui-raw-space-6)', alignItems: 'center' }}>
  <Skeleton shape="avatar" size="sm" />
  <Skeleton shape="avatar" size="md" />
  <Skeleton shape="avatar" size="lg" />
</div>

Rows

List rows that have not arrived: the 32px row track, one bar each.

<div style={{ width: 260 }}>
  <Skeleton shape="row" width="64%" />
  <Skeleton shape="row" width="48%" />
  <Skeleton shape="row" width="72%" />
</div>

Controls

A button or field before it loads, at the control track and radius.

<div style={{ display: 'flex', gap: 'var(--fui-raw-space-8)', alignItems: 'center' }}>
  <Skeleton shape="control" size="sm" />
  <Skeleton shape="control" size="md" />
  <Skeleton shape="control" size="lg" />
</div>

Card Skeleton

Media, title, then body — the card before it loads.

<div
  style={{
    width: 300,
    padding: 'var(--fui-raw-space-12)',
    border: '1px solid var(--fui-border)',
    borderRadius: 'var(--fui-radius-surface)',

API reference

Component props
Prop
Type
Default
Description
fillboolean | undefinedfalseFill parent container
heightstring | number | undefinedNot setCustom height (string or number)
shapeSkeletonShape | undefinedrecttextheadingrowcontrolavatarrectSemantic shape that auto-sizes: a 10px text bar, a 12px heading bar, an avatar square, a control at its track, a 32px row, or a rect
sizeSkeletonSize | undefinedsmmdlgmdSize for the avatar (24, 32, 40) and control (28, 32, 40) shapes
Text.lastLineWidthnumber | undefinedNot setWidth of last line as percentage. Creates natural paragraph appearance.
Text.linesnumber | undefinedNot setNumber of text lines to render
widthstring | number | undefinedNot setCustom width (string or number)
styleCSSProperties | undefinedNot set—

Accessibility

  • Skeletons are decoration — hide them with aria-hidden
  • Announce the loading state separately if it matters
  • Keep the pulse subtle but visible against the surface
  • ProgressalternativeUse Progress for determinate loading

Next steps