Skeleton

A soft placeholder in the shape of content that is still loading.

import { Flex } from "@hel/ui/flex"
import { Skeleton, SkeletonGroup } from "@hel/ui/skeleton"
import { Stack } from "@hel/ui/stack"

When to use it

Use a skeleton while content is on its way and you know roughly what it will look like: a profile, a table, a card. It holds the space so the page does not jump when the real content arrives, and it tells people something is coming.

A skeleton should look like what it replaces, in the same sizes, so the change is smooth. For a short wait, or when you cannot guess the shape, use a Spinner. For a task with a known length, use a Progress bar.

If you load data, you may not need to place skeletons yourself. Async Content takes a skeleton and shows it at the right moment, without flashing on fast loads.

Import

import {
  Skeleton,
  SkeletonText,
  SkeletonGroup,
  TableSkeleton,
  ListSkeleton,
  CardSkeleton,
  FormSkeleton,
  StatSkeleton,
} from "@hel/ui/skeleton"

Usage

Skeleton is one soft block. Give it a width and a height, or a size for both, and a shape: rounded, the default, square, pill or circle. Put several together to draw the shape of your content.

Skeletons are hidden from screen readers, because a block has no meaning. Wrap the set in a SkeletonGroup with a label that says what is loading, and it is announced once, as "Loading profile".

<SkeletonGroup label="Loading profile">
  <Flex gap="lg" align="center">
    <Skeleton shape="circle" size={48} />
    <Stack gap="sm">
      <Skeleton width={160} height={14} shape="pill" />
      <Skeleton width={110} height={12} shape="pill" />
    </Stack>
  </Flex>
</SkeletonGroup>

SkeletonText draws lines of text for you. lines is how many, size is sm, md or lg for the height of a line, and the last line is shorter, which lastLineWidth can change.

Animation

animation is pulse, the default, which fades in and out. shimmer sweeps a soft highlight across, and none is still. Both moving styles stop for people who prefer reduced motion. A SkeletonGroup sets the animation for everything inside it.

The shimmer can be tuned. shimmerAngle is the slant of the band in degrees, 20 by default, and it mirrors in right-to-left layouts. shimmerStrength is how bright it is, from 0 to 1.

pulse

shimmer

none

import { Flex } from "@hel/ui/flex"
import { Skeleton } from "@hel/ui/skeleton"
import { Stack } from "@hel/ui/stack"

Examples

Swapping for the content

loading turns a single skeleton into its content. When it is false, the skeleton renders its children instead, so a component can go from skeleton to content without changing its structure. For a whole region, show the skeleton while loading and the real thing after, as this example does.

import * as React from "react"

import { Avatar, AvatarFallback } from "@hel/ui/avatar"

Ready-made skeletons

For common shapes, use the presets: TableSkeleton, ListSkeleton, CardSkeleton, FormSkeleton and StatSkeleton. Each takes a label and an animation, and its own options, such as rows and columns for the table. A TableSkeleton takes the density of the table it stands for, so the rows are as tall.

TableSkeleton

ListSkeleton

CardSkeleton

FormSkeleton

StatSkeleton

import {
  CardSkeleton,
  FormSkeleton,

Props

Skeleton

PropTypeDefault
width / heightnumber | stringnone
sizenumber | stringnone
shape"rounded" | "square" | "pill" | "circle""rounded"
animation"pulse" | "shimmer" | "none""pulse"
shimmerAnglenumber20
shimmerStrengthnumber1
loadingbooleantrue

A number is in pixels. A string can be any CSS length, such as "60%" or "12rem".

SkeletonGroup

PropTypeDefault
labelstring"Loading"
animation"pulse" | "shimmer" | "none"none
shimmerAngle / shimmerStrengthnumbernone

SkeletonText

PropTypeDefault
linesnumber3
size"sm" | "md" | "lg""md"
lastLineWidthnumber | string"60%"
shape"rounded" | "square" | "pill" | "circle""pill"