BridgeHandbookDraft
For agents
Menu
ExperimentalatomFeedback

Skeleton

A grey placeholder shaped like content that hasn't loaded yet.

import { … } from "@brdgapp/ui/components/skeleton"

Usage

Skeleton is a grey placeholder shaped like content that hasn't loaded yet: a text line, a circle for a face, or a block. Build a loading state from skeletons the same size and shape as the real content, so nothing moves when the data arrives. The usual mistake is one big spinner, or skeletons that don't match what replaces them.

When to use

  • The loading body of a card, table or list, passed to DataState's skeleton.
  • Text lines, faces and logos, and blocks such as charts, while their data loads.

When not to use

  • For an action that's running, like Save. Use the button's loading. Use Button instead.
  • For a button whose data hasn't loaded. Use ButtonSkeleton, which matches the button's height. Use Button instead.
  • To hold up a whole page. Each part loads on its own, with its own skeleton.

The look comes from props

A component's appearance comes only from its own options (variant, size, color). className on a package component is for layout (mt-4, shrink-0, w-full), never for font size, color, padding, radius or hover. If the look needs a className, the component or the chosen option is wrong: fix that instead.

Each part loads on its own

The page frame shows straight away; each card, table or list shows its own skeleton, then its data. Never hold the whole page behind one if (isLoading) return <Skeleton />: a slow part would block the fast ones.

Reserve the space

Nothing that arrives later may push content down. A skeleton has the same height and shape as what it stands for, measured, not guessed. A visible jump after the first paint is a bug.

Anatomy

  1. Line. 12px tall, 4px corners. Give it a width (w-1/2, w-24) and, for bigger text, a height (h-4).

  2. Circle. Round. Give it a size matching the face or logo it stands in for (size-8).

  3. Block. 12px corners. Give it the size of the chart, image or card body it stands in for.

Examples

A list, loading

The same rows, faces and lines the real list will have. The card and title stay.

People you know

Line, circle and block

States

StateWhat happens
Loading--surface-tertiary, pulsing gently. With reduced motion, it stays still.

Behavior

  • Hidden from screen readers (aria-hidden); the region that's loading says so, which DataState sets up.
  • Shape comes from shape; size and position from className (w-1/2, size-8, h-24). Never a color or a corner through className.

Do and don't

Do

Draw the same rows, faces and lines the content will have, at the same sizes.

Don't

Show one generic block, or a spinner, where a list of people will appear.

Do

Keep the card and its title on screen; only the inside is skeletons.

Don't

Replace the whole card, title and all, with a grey box.

Content

  • No text. A skeleton says nothing; the shapes do.

Accessibility

  • Skeletons are hidden from screen readers. Mark the loading region busy instead (DataState does).

API reference

Skeleton

One placeholder shape.

PropTypeDefaultDescription
shape"line" | "circle" | "block""line"Text line (4px corners), circle, or block (12px corners).
classNamestring—Size and position only: w-1/2, size-8, h-24.

Lineage

What it's built from and where it's used, read from the code. When you change it, check these too.

Built from
Nothing else in the package
Used by components
None
Used in patterns
None
Used in templates
None

Sources

  • shadcn skeleton (Base UI style), pulled with npm run ui-add, then put on Bridge's token.
  • Bridge OS: about 170 hand-made bg-[var(--surface-tertiary)] animate-pulse placeholders in 40 files (main, 5d9b382).