Skeleton is the single shimmer-block primitive the kit ships. Compose it to mock the
layout of whatever you're loading — list rows, card content, headers — instead of
peppering your codebase with one-off bg-fill-2 rounded-full w-[X] utility classes.
The background tone is var(--fill-2) so brand themes recolour it for free, and the
shimmer keyframe respects prefers-reduced-motion: reduce for accessibility.
Pill bars
width / height take numbers (pixels) or any CSS length string. Default radius is 999px
(pill), giving a single text-line look out of the box.
import { Flex, Skeleton } from "@appboxo/ui-kit"
import { PreviewLayout, Row, Section } from "./_section"
export function SkeletonBarPreview() {
return (
<PreviewLayout>
<Section
title="Pill bars"
description="Numbers map to pixels; pass any CSS length string for percentage / rem widths."
>
<Row label="width={200} height={16}">
<Skeleton width={200} height={16} />
</Row>
<Row label='width="100%" height={12}'>
<Skeleton width="100%" height={12} />
</Row>
<Row label="width={120} height={10}">
<Skeleton width={120} height={10} />
</Row>
</Section>
<Section title="Static (no shimmer)">
<Flex vertical={false} gap={12} align="center">
<Skeleton width={140} height={14} noAnimation />
<Skeleton width={80} height={14} noAnimation />
</Flex>
</Section>
</PreviewLayout>
)
}
Avatars + status dots
circle overrides radius and forces a perfect circle.
import { Flex, Skeleton } from "@appboxo/ui-kit"
import { PreviewLayout, Row, Section } from "./_section"
export function SkeletonCirclePreview() {
return (
<PreviewLayout>
<Section
title="Avatars / status dots"
description="`circle` wins over `radius` and forces a perfect circle."
>
<Row label="width=44, height=44, circle">
<Skeleton width={44} height={44} circle />
</Row>
<Row label="width=32, height=32, circle">
<Skeleton width={32} height={32} circle />
</Row>
<Row label="cluster of circles + bars">
<Flex vertical={false} align="center" gap={12}>
<Skeleton width={44} height={44} circle />
<Flex gap={6}>
<Skeleton width={140} height={14} />
<Skeleton width={90} height={12} />
</Flex>
</Flex>
</Row>
</Section>
</PreviewLayout>
)
}
List row composition
The point of a primitive: mock a real list row by composing <Skeleton> inside the same
<Flex> / <Card> structure you'd use after data loads. Keeping the surrounding layout
identical between loading and loaded states means the page doesn't reflow when data arrives.
import { Card, Flex, Skeleton } from "@appboxo/ui-kit"
import { PreviewLayout, Section } from "./_section"
export function SkeletonListRowPreview() {
return (
<PreviewLayout>
<Section
title="List row mock"
description="Compose Skeleton primitives inside the same Flex / Card structure you'd use after data loads — keeps the page from reflowing when content arrives."
>
<Flex gap={8}>
{Array.from({ length: 4 }).map((_, i) => (
<Card key={i} style={{ background: "var(--fill-1)" }}>
<Flex vertical={false} align="center" gap={12}>
<Skeleton width={44} height={44} circle />
<Flex gap={6}>
<Skeleton width={140} height={14} />
<Skeleton width={90} height={12} />
</Flex>
</Flex>
</Card>
))}
</Flex>
</Section>
</PreviewLayout>
)
}
Card content
Heavier mock for richer card content — a header row + a 3-line body. Demonstrates mixing
width={"100%"} (string) with width={220} (number).
import { Card, Flex, Skeleton } from "@appboxo/ui-kit"
import { PreviewLayout, Section } from "./_section"
export function SkeletonCardPreview() {
return (
<PreviewLayout>
<Section
title="Card content mock"
description="Header row + 3-line body. Mixes `width={'100%'}` (string) and `width={220}` (number) to show both length APIs."
>
<Card style={{ background: "var(--fill-1)" }}>
<Flex gap={16}>
<Flex vertical={false} align="center" gap={12}>
<Skeleton width={24} height={24} circle />
<Skeleton width={180} height={12} />
</Flex>
<Flex gap={8}>
<Skeleton width="100%" height={12} />
<Skeleton width="100%" height={12} />
<Skeleton width={220} height={12} />
</Flex>
</Flex>
</Card>
</Section>
</PreviewLayout>
)
}
API
| Prop | Type | Default | Notes |
|---|---|---|---|
width | number | string | — | Number → pixels. String → any CSS length ("100%", "12rem", "calc(100% - 16px)"). |
height | number | string | — | Same convention as width. |
circle | boolean | false | Force a circle. Wins over radius. |
radius | number | string | 999 | Border radius. Number → pixels. Defaults to a pill. |
noAnimation | boolean | false | Stop the shimmer (e.g. permanently empty placeholders by design). |
| ...rest | HTMLAttributes<HTMLDivElement> | — | className, style, aria-*, etc. are all forwarded. |
Installation
pnpm add @appboxo/ui-kit