Skip to content
View as Markdown

Feedback

Skeleton

A placeholder shown while content loads, sized by the content it stands in for.

Import

TSX
import { Skeleton, Avatar } from "@loamui/core";

Usage

Every example has a CSS tab. That’s the real, complete stylesheet for the component: plain, static CSS, with nothing running in the browser.

Basic lines

Stack skeletons to stand in for text while it loads. A bare Skeleton is one line tall (1lh) in the local typography and full width; a shorter line is a CSS decision, through the public --loam-skeleton-inline-size property.

Circle + lines

An avatar-and-text placeholder for a list item. The circle is a wrapped Avatar: the child sizes and shapes the placeholder, so there is no circle prop and nothing to keep in step with the avatar's size.

A bare placeholder from CSS

Where there is no content to wrap, a thumbnail's size and shape are CSS decisions: --loam-skeleton-inline-size, --loam-skeleton-block-size and --loam-skeleton-radius, set per instance or on a region.

Wrap real content

Wrapped children size the box, so the placeholder matches the coming layout with no size props; flip visible when the data lands.

Guidance

When to use it

  • While loading content whose shape you already know: the skeleton mirrors the coming layout, so the swap to real content is a fill-in, not a rearrangement.
  • To hold the loaded content's space open and avoid layout shift while data arrives.

When not to

  • When you cannot predict what the loaded layout looks like. A skeleton that does not match what replaces it makes the swap more jarring than showing nothing, and perceived performance gets worse, not better. Use Loader.
  • For an operation that is not producing visible content in that spot (saving, deleting, background work). A skeleton promises content that never comes; use Loader next to the affected control.

Accessibility

  • The root renders aria-hidden: skeletons are never announced. Screen-reader users hear the real content when it arrives instead of a stream of meaningless placeholders. Mark the region they fill aria-busy while they show.
  • Because skeletons are silent, announce the wait elsewhere if it needs announcing: a Loader (which renders role="status") or a visually hidden status message.
  • The moving shimmer is gated behind prefers-reduced-motion: no-preference. Reduced-motion users get the same placeholder with a static gradient, with no override needed because the motion is opt-in.
  • While visible, wrapped children keep their layout but paint nothing (visibility: hidden inherits through the whole subtree) and are unreachable by pointer and text selection, so nothing interactive is exposed before it is real.

How it works

Match the shape you are loading

Build the skeleton from the loaded UI's real dimensions: the avatar's diameter, the text's line heights, the thumbnail's radius. The entire benefit of a skeleton is that the eye has already parsed the layout before the content lands; a placeholder of a different shape spends that benefit and charges interest.

Swap in place with visible

Wrap the real content and flip visible to false when it is ready. The wrapped children size the placeholder themselves, so it mirrors the coming layout without declared dimensions; the public custom properties exist for bare placeholders, where the absent content cannot be measured. While the skeleton is visible, children are hidden from pointer, selection and assistive tech, so nothing half-loaded leaks out.

Say the region is busy

Skeletons are silent, so the region they fill should not be. Put aria-busy="true" on the region while its skeletons show and remove it when the content lands: assistive technology then knows the region is being updated rather than empty, and can hold announcements until it settles. A wait that needs announcing gets a Loader or a visually hidden status message beside the region.

Props

PropTypeDefaultDescription
visiblebooleantrueWhen false, render children instead of the placeholder.
childrenReactNode—Real content, shown once visible is false.
...othersHTMLAttributes<HTMLDivElement>—All native <div> props are forwarded.

Custom properties

PropertySyntaxDefaultDescription
--loam-skeleton-inline-sizeCSS length100% (bare), fit-content (wrapped)Inline size of a bare placeholder, where there is no content to measure.
--loam-skeleton-block-sizeCSS length1lh (bare), auto (wrapped)Block size of a bare placeholder.
--loam-skeleton-radiusCSS lengthvar(--loam-radius-md); var(--loam-radius-full) around an AvatarCorner rounding of the placeholder; set per instance or on a region.