Skeleton
A loading placeholder primitive for text, cards, media objects, and custom layouts.
API Reference
Skeleton is a moduix loading primitive and does not wrap a Base UI primitive.
Use Skeleton, SkeletonRect, and SkeletonCircle for placeholders, then compose rows and
columns with SkeletonRow and SkeletonColumn. Skeleton parts are marked aria-hidden because
they describe loading shape, not user-readable content.
Basic
import { Skeleton, SkeletonColumn } from "moduix";export function SkeletonDemo() { return ( <SkeletonColumn gap={10}> <Skeleton height={18} /> <Skeleton height={18} width="86%" /> <Skeleton height={18} width="64%" /> </SkeletonColumn> );}Full list of component variables available for project-level overrides.
| Property | Default | Description |
|---|---|---|
| --skeleton-animation | var(--animation-pulse) | Controls skeleton loading animation. |
| --skeleton-bg | color-mix(in oklab, var(--color-muted-foreground) 18%, var(--color-background)) | Controls skeleton background color. |
| --skeleton-radius | var(--radius-md) | Controls default skeleton border radius. |
Interactive variables scoped for docs preview without changing size scale tokens.
| Property | Value | Default | Description |
|---|---|---|---|
| --skeleton-animation | var(--animation-pulse) | Controls skeleton loading animation. | |
| --skeleton-bg | color-mix(in oklab, var(--color-muted-foreground) 18%, var(--color-background)) | Controls skeleton background color. | |
| --skeleton-radius | var(--radius-md) | Controls default skeleton border radius. |
Anatomy
Skeleton is composed from visual placeholder blocks and optional layout helpers. Use the shape
parts for the visible loading state and the layout parts only when they match the spacing of the
loaded UI.
SkeletonColumn
├─ Skeleton
├─ Skeleton
└─ Skeleton<SkeletonColumn gap={10}>
<Skeleton height={18} />
<Skeleton height={18} width="86%" />
<Skeleton height={18} width="64%" />
</SkeletonColumn>| Part | Role |
|---|---|
Skeleton | Base placeholder block. Use width, height, radius, grow, and animated to size it. |
SkeletonRect | Rectangular shortcut for cards, media, and panels. It defaults to a taller block height. |
SkeletonCircle | Circular shortcut for avatars, icons, and round media. It uses one size for width and height. |
SkeletonRow | Horizontal layout helper with gap, pt, pb, and mobileStack spacing controls. |
SkeletonColumn | Vertical layout helper with gap, pt, pb, and grow spacing controls. |
Skeleton does not use portal-like service layers such as portal, positioner, backdrop, or
viewport. In most cases, keep the default shape styling and adjust dimensions with props. Use
className and the CSS variables only when the placeholder needs to match a custom surface.
Composition
Use Skeleton directly for text lines and small custom blocks. Use SkeletonRect and
SkeletonCircle when the placeholder has a clear geometric shape, then place those shapes inside
SkeletonRow or SkeletonColumn to mirror the final layout.
All parts accept className and standard div props. The component exposes --skeleton-bg,
--skeleton-radius, and --skeleton-animation for targeted styling; avoid broad overrides in a
shared parent when a page demonstrates multiple skeleton styles.
Examples
Card
Use SkeletonRect with smaller line skeletons to reserve the shape of card content while data is loading.
import { Skeleton, SkeletonColumn, SkeletonRect } from "moduix";import styles from "./skeleton-card-demo.module.css";export function SkeletonCardDemo() { return ( <div className={styles.card}> <SkeletonRect height={148} radius="var(--radius-lg)" /> <SkeletonColumn gap={12}> <Skeleton height={20} width="70%" /> <Skeleton height={14} /> <Skeleton height={14} width="82%" /> </SkeletonColumn> </div> );}.card { display: flex; width: min(20rem, calc(100vw - var(--spacing-8))); flex-direction: column; gap: var(--spacing-4);}Media Object
Combine SkeletonCircle, SkeletonColumn, and grow when mirroring avatar rows, list items, and comments.
import { Skeleton, SkeletonCircle, SkeletonColumn, SkeletonRow } from "moduix";import styles from "./skeleton-media-object-demo.module.css";export function SkeletonMediaObjectDemo() { return ( <SkeletonRow className={styles.mediaObject} gap={12}> <SkeletonCircle size={48} /> <SkeletonColumn grow gap={8}> <Skeleton height={16} width="46%" /> <Skeleton height={14} /> <Skeleton height={14} width="72%" /> </SkeletonColumn> </SkeletonRow> );}.mediaObject { width: min(24rem, calc(100vw - var(--spacing-8)));}Layout Props
Use gap, pt, pb, grow, and mobileStack on the layout primitives when the skeleton must
reserve the same spacing as the loaded UI.
import { Skeleton, SkeletonColumn, SkeletonRow } from "moduix";import styles from "./skeleton-layout-props-demo.module.css";export function SkeletonLayoutPropsDemo() { return ( <SkeletonColumn className={styles.layoutProps} gap={12} pt={4} pb={4}> <SkeletonRow gap={12} mobileStack={false}> <Skeleton width={72} height={48} /> <SkeletonColumn grow gap={8}> <Skeleton height={14} width="62%" /> <Skeleton height={14} /> </SkeletonColumn> </SkeletonRow> <SkeletonRow gap={12} mobileStack={false}> <Skeleton width={72} height={48} /> <SkeletonColumn grow gap={8}> <Skeleton height={14} width="48%" /> <Skeleton height={14} /> </SkeletonColumn> </SkeletonRow> </SkeletonColumn> );}.layoutProps { width: min(24rem, calc(100vw - var(--spacing-8)));}Static
Pass animated={false} when motion is unnecessary or when the placeholder appears in dense repeated layouts.
import { SkeletonRect } from "moduix";export function StaticSkeletonDemo() { return <SkeletonRect width={320} height={72} animated={false} />;}Class Names
Pass className to the root or layout primitives when styling with CSS Modules, Tailwind CSS, or CSS-in-JS.
import { Skeleton, SkeletonColumn } from "moduix";import styles from "./custom-skeleton-demo.module.css";export function CustomSkeletonDemo() { return ( <SkeletonColumn className={styles.customBlock} gap={10}> <Skeleton className={styles.customSkeleton} height={18} /> <Skeleton className={styles.customSkeleton} height={18} width="78%" /> <Skeleton className={styles.customSkeleton} height={18} width="52%" /> </SkeletonColumn> );}.customBlock { display: flex; width: min(20rem, calc(100vw - var(--spacing-8))); --skeleton-bg: var(--color-primary); --skeleton-radius: var(--radius-full); --skeleton-animation: none;}.customSkeleton { opacity: 0.28;}