Skip to content

Skeleton

Skeleton loaders indicate that content is loading by masking supported components in place or by rendering explicit placeholder blocks.

Import

import {
UNSTABLE_Skeleton,
SkeletonPrimitive,
getSkeletonClassName,
useSkeleton,
useSkeletonClassName,
} from "@cfa/react-core";

Skeleton is currently exported as an unstable component while the API is refined through beta feedback.

Live Editor

Patterns

Skeleton supports two loading patterns: wrapped content with useSkeleton, and manual SkeletonPrimitive blocks.

Content masking

Wrap real layout and mock content in UNSTABLE_Skeleton. When isLoading is true, the wrapper sets aria-busy, applies inert, and provides loading context to descendants through useSkeleton(). Full inert behavior (blocking focus and interaction inside the shell) requires React 19; on React 17/18, aria-busy and skeleton masking still apply.

Supported components apply their own skeleton styles when they read that context. Use CFA components in the layout rather than raw HTML when you want masking.

Supported components

These components call useSkeleton() and apply skeleton styles when wrapped in a loading UNSTABLE_Skeleton:

  • Avatar
  • Button and LinkButton
  • Card.Subtitle, Card.Body, and Card.Media (Card.Title via Heading)
  • Heading
  • Link
  • ProgressBar
  • Table.Cell and Table.Column
  • Text

Primitive placeholders

Use SkeletonPrimitive when you need full control over the loading layout, or when the real component is not wired to useSkeleton. Set width, height, and borderRadius to match the content you are loading.

Custom components

Use useSkeletonClassName() in your own components to apply the shared mask classes from the nearest loading Skeleton:

import { cx } from "class-variance-authority";
import { useSkeletonClassName } from "@cfa/react-core";
function MyWidget({ className, children, ...props }) {
const skeletonClassName = useSkeletonClassName();
return (
<div {...props} className={cx(className, skeletonClassName)}>
{children}
</div>
);
}

For text-shaped placeholders, pass { variant: "text" }. When nested icons or images should be hidden while loading, add { hideContent: true }.

const skeletonClassName = useSkeletonClassName({ variant: "text", hideContent: true });

Examples

Profile

Table

Progress bar

Usage guidance

  • Wrap real CFA components with placeholder content to preserve layout and spacing.
  • Full inert behavior on the loading shell requires React 19. On React 17/18, aria-busy and skeleton masking still apply.
  • Raw HTML such as <p> or <img> is not masked automatically. Use a wired component (Text, Card.Media, and so on) or compose SkeletonPrimitive blocks instead.
  • Form controls (Select, TextInput, Checkbox, and similar) are not masked. Use SkeletonPrimitive for field-shaped placeholders, or leave fields out of the loading layout.
  • Compose multiple SkeletonPrimitive elements when you need custom rows or form-field layouts.
  • Chip is exported from @cfa/react-core/server and does not read useSkeleton(). Use SkeletonPrimitive for chip-shaped placeholders instead.