ComponentsFeedback

Skeleton

Holds the space of content that is still loading. Use it for rows, titles, panels, and table cells, so the page does not jump when data arrives.

Shapes

Six footprints, each the size of the content it stands in for. Line is the default.

shape="line"16px tall, full width. Chat, file, and project rows.
shape="title"24px tall, two thirds wide. Plan and canvas headings.
shape="avatar"32px circle. Member and account pictures.
shape="panel"128px tall, full width. Workflow diagrams and invitations.
shape="checkbox"16px box. Table row selection.
shape="action"28px square. Row edit and delete buttons.

Loading lists

SkeletonGroup is a status region. Its label names what is loading and is read as hidden text. Gap is 8, 12, or 16px.

gap="sm"
Checking what will be deleted…
gap="md"
Loading access…
gap="lg"
Loading Google Drive files…

Canvases and details

Title, line, and panel shapes build the outline of a canvas or a diagram before it loads.

Plan
Loading Spring launch plan…
Canvas item
Loading Reschedule design review…
Workflow diagram
Loading workflow diagram…
Slack destinations
Loading Slack destinations…

Table rows

Checkbox and action shapes hold the selection and button cells. Hide an action skeleton when the person cannot use that action.

SelectCropBedSownHarvestActions

With visible status text

When the wait needs words, show them. Leave the group without a label and put the sentence inside it.

Loading GitHub accounts…

Checking who in “Greenhouse rotation” can open them…

Loading deliveries…

In cards and rows

Skeletons fill the body of a bordered card, or sit beside an icon in an event row.

Loading upcoming meetings…
Loading event details…
Loading member…

Sidebar menu rows

className and style pass through. A small icon square and a text bar capped by a width variable stand in for each menu row.

Loading chats…
  • Weekly harvest
  • Seed order
  • Shared files
  • Inbox

Load states

The skeleton keeps the list height while it loads, then gives way to rows, an empty state, or an error with a retry.

Loading projects…

Usage

Skeleton usageTSX
import { Skeleton, SkeletonGroup } from "@tulipfarm/sprout/skeleton";

<SkeletonGroup label="Loading projects" gap="lg">
  <Skeleton />
  <Skeleton />
  <Skeleton />
</SkeletonGroup>

<SkeletonGroup label="Loading workflow diagram">
  <Skeleton shape="title" />
  <Skeleton shape="panel" />
</SkeletonGroup>

Skeleton renders an empty div. shapesets its size:

  • "line", the default: 16px tall, full width.
  • "title": 24px tall, two thirds wide.
  • "avatar": a 32px circle.
  • "panel": 128px tall, full width.
  • "checkbox": a 16px box.
  • "action": a 28px square, the size of a small icon button.

className, style, and data-*attributes pass through, so you can set a custom size or a width variable for one placeholder.

SkeletonGroup stacks skeletons in a statusregion. gap is "sm" (8px), "md"(12px, the default), or "lg" (16px). Use renderto change the element, for example to a section.

Table rows

Placeholder table rowsTSX
<Table aria-label="Records" data={rows} columns={columns} getRowId={(row) => row.id} loading={isLoading} />

Visible status text

Group with a sentenceTSX
<SkeletonGroup gap="sm">
  <p>Loading GitHub accounts…</p>
  <Skeleton />
  <Skeleton />
</SkeletonGroup>

When the wait needs words, put them inside the group and leave outlabel. The sentence then names the region.

Menu rows

Icon and capped text barTSX
<div className="flex h-8 items-center gap-2 px-2">
  <Skeleton className="size-4 rounded-md" />
  <Skeleton
    className="h-4 max-w-(--skeleton-width) flex-1"
    style={{ "--skeleton-width": "72%" } as CSSProperties}
  />
</div>

Accessibility

Each skeleton is aria-hidden, so screen readers skip it.SkeletonGroup has role="status". Itslabel becomes the accessible name and is also rendered as visually hidden text, such as “Loading projects…”. Skeletons are not focusable and ignore the pointer.

The pulse runs every 1.6 seconds and stops when reduced motion is requested. In forced colors mode each skeleton gets a visible border.

Theme tokens

The fill is --sprout-secondary. Corners follow--sprout-radius-control.