Skip to content

Skeleton

Reserve space for content while it loads.

import { Skeleton } from '@youj/react'

Installation & usage

npm i @youj/react

For an app already set up with npx @youj/cli init, which installs the peers (@youj/core, @youj/motion, motion and Base UI) and wires the provider. New app? Start at Installation.

Use Skeleton when you know the shape of the loading content, such as rows, a profile or a card. Use a Spinner for short waits without a known layout, or Progress for measurable work.

A skeleton is a block. Size and round it with classes to match the content it stands in for.

import { Skeleton } from '@youj/react'

export function ProfileSkeleton() {
  return (
    <div className="flex items-center gap-3">
      <Skeleton className="size-10 rounded-full" />
      <div className="grid flex-1 gap-2">
        <Skeleton className="h-3.5 w-3/5" />
        <Skeleton className="h-3 w-2/5" />
      </div>
    </div>
  )
}

Reference

PropsOptions

Props

Skeleton adds no props of its own. They pass through to ComponentProps<'div'>.

MotionShimmer · reduced motion

Motion

Motion tokens
MomentTokens
Shimmera band of light crosses in the first 60% of each cycle, then rests
duration.deliberateease.standard

560ms · cubic-bezier(0.2, 0, 0, 1) · × 3 per cycle

560mscubic-bezier(0.2, 0, 0, 1)× 3 per cycle

Reduced motion

The band is gone and the blocks hold still.

The cycle is three deliberate beats, so the band reads as a calm pulse rather than a constant wash. It follows the Speed control, and it runs the other way under right-to-left.

Reduced motion

The traveling band is removed and each block is a still, recessed shape. The layout still shows where content will land.

AccessibilityARIA, announcements

Accessibility

  • Skeletons are aria-hidden: a pile of empty blocks means nothing to a screen reader.
  • Put aria-busy on the region they stand in for, and announce the wait once in a polite status, such as "Loading members". ContentSwap does both with loadingLabel.
  • Don't leave skeletons up once the content has failed. Swap them for an error with a way to retry.