apx-dsv0.1Local renderer · live
K
Foundations4
  • Getting started
  • Theming
  • Templates
  • Icons
  • Avatar
  • Badge
  • DataGrid
  • Scheduler
  • Stat
  • Table
  • Timeline
  • TreeView
  • Accordion
  • Alert
  • EmptyState
  • Progress
  • Skeleton
  • Spinner
  • SplashScreen
  • Toast
  • ColorPicker
  • FileUpload
  • Form
  • Rating
  • TagsInput
  • Combobox
  • Field
  • Select
  • Toggle
  • Button
  • Calendar
  • Checkbox
  • DatePicker
  • Input
  • NumberInput
  • Radio
  • Slider
  • Switch
  • Textarea
  • AppShell
  • Div
  • Divider
  • Sidebar
  • Stack
  • Typography
  • Image
  • Breadcrumbs
  • Carousel
  • NavigationMenu
  • Pagination
  • Stepper
  • Tabs
  • Toolbar
  • CommandPalette
  • Confirm
  • Drawer
  • HoverCard
  • Menu
  • Modal
  • Popover
  • Tooltip
  • Icon
  • Card
  • PricingCard
60 componentsapx-ds/renderer
Skeleton
Variant↳ other

Feedback

Skeleton

Loading placeholder primitive. Three exports on one shared recipe — block <Skeleton>, multi-line <SkeletonText>, and <Avatar>-size-matched <SkeletonAvatar>. Pure CSS shimmer / pulse / none, no Motion library, prefers-reduced-motion aware.

Skeleton

The canonical loading-placeholder primitive. <Skeleton /> claims layout space for content that isn't ready yet, and animates lightly so users can tell the page is alive without demanding their attention.

Overview — avatar + text, card, and list-item patterns

Loading preview…
Overview.tsx

Three exports ship on a single shared recipe:

  • <Skeleton /> — the block primitive; any width × height × radius.
  • <SkeletonText /> — multi-line text placeholder; auto-shortens the last line.
  • <SkeletonAvatar /> — circular placeholder sized 1:1 with <Avatar />.

Why this exists

Pages that re-flow when data arrives feel broken. Skeletons solve two jobs:

  1. Reserve space. The layout doesn't jump when content lands — width / height on the placeholder match the eventual content's box.
  2. Signal liveness. A subtle shimmer or pulse tells the user "we're still loading" without spinning at them. Both animations halt under prefers-reduced-motion.

Anatomy

tsx
┌──────────────────────────────────────────────────────┐
│  ░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░  │  ← <Skeleton>
└──────────────────────────────────────────────────────┘

┌──────────────────────────────────────────────────────┐
│  ░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░  │
│  ░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░  │  ← <SkeletonText>
│  ░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░ (shortened)         │
└──────────────────────────────────────────────────────┘

        ●  ← <SkeletonAvatar>  (rounded-full, size matches <Avatar>)
┌──────────────────────────────────────────────────────┐
│  ░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░  │  ← <Skeleton>
└──────────────────────────────────────────────────────┘

┌──────────────────────────────────────────────────────┐
│  ░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░  │
│  ░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░  │  ← <SkeletonText>
│  ░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░ (shortened)         │
└──────────────────────────────────────────────────────┘

        ●  ← <SkeletonAvatar>  (rounded-full, size matches <Avatar>)

Variants

VariantBackgroundWhen to reach for it
solidneutral bg-subtleDefault. The "every-page" loading state.
soft<color>-subtleBrand-immersive splashes / status-aware skeletons (e.g. danger).

Animations

AnimationBehaviorWhen
shimmerLeft-to-right gradient sweep (1.6s loop, linear)Default. The modern UX.
pulseOpacity oscillation (1.8s loop)When the shimmer highlight can't
contrast against the surface.
noneStatic low-opacity blockReduced-motion-by-default pages,
nested compositions, lazy loads.

All three respect prefers-reduced-motion: the animation halts and a 70% opacity fallback paints in its place. ARIA semantics are unchanged.

Examples

Default — a single block placeholder

Loading preview…
Basic.tsx

SkeletonText — multi-line, auto-shortened last line

Loading preview…
Text.tsx

SkeletonAvatar — every Avatar-aligned size

Loading preview…
Avatar.tsx

solid vs soft

Loading preview…
Variants.tsx

shimmer / pulse / none

Loading preview…
Animations.tsx

Roundedness — none / sm / md / lg / full

Loading preview…
Rounded.tsx

Every role color × soft variant

Loading preview…
Colors.tsx

Canonical 'card while loading' composition

Loading preview…
Card.tsx

Avatar + text-line combo (common UI pattern)

Loading preview…
ListItem.tsx

Full-page skeleton mockup

Loading preview…
Page.tsx

Width / height combos

Loading preview…
Sizes.tsx

Props

<Skeleton />

PropTypeDefaultNotes
widthstring | number—Number coerces to px. String passes through.
heightstring | number—Number coerces to px.
rounded'none' | 'sm' | 'md' | 'lg' | 'full''md'Maps to the theme's radius scale (Katana/Tetsu/Origami flow through).
animation'shimmer' | 'pulse' | 'none''shimmer'All three halt under prefers-reduced-motion.
variant'solid' | 'soft''solid'Responsive via the { base, md } shape.
color'primary' | 'secondary' | 'success' | 'warning' | 'danger' | 'info' | 'neutral''neutral'Only visible under variant="soft".
classNamestring—Merged via tailwind-merge. Last-wins.
sxSx—Theme-aware inline style.

<SkeletonText />

Extends <Skeleton /> (minus width) with:

PropTypeDefaultNotes
linesnumber3Coerced to Math.max(1, Math.floor(lines)).
spacing'sm' | 'md' | 'lg''md'Vertical gap between lines.
lastLineWidthstring | number'60%'Last line shortens for typographic realism.
heightstring | number14Per-line height; matches a body font line-height by default.

<SkeletonAvatar />

Extends <Skeleton /> (minus width / height / rounded) with:

PropTypeDefaultNotes
size'xs' | 'sm' | 'md' | 'lg' | 'xl' | '2xl' | number'md'Mirrors <Avatar size> 1:1. Numeric = px.

Accessibility

  • Root announces once. <Skeleton /> and <SkeletonText /> carry role="status" and aria-label="Loading" so screen readers report the loading state. Override aria-label to localize or to disambiguate ("Loading user profile", "Loading messages", …).
  • Nested lines are silent. Each <Skeleton /> line inside <SkeletonText /> is aria-hidden="true" — only the wrapper announces. The same escape hatch is available on a bare <Skeleton aria-hidden="true" /> when you want to nest one inside a custom wrapper that owns the announcement.
  • Reduced motion. prefers-reduced-motion: reduce halts the shimmer / pulse animation via the motion-reduce: Tailwind variant and drops opacity to 70% so the placeholder is still visually distinct from the surface.
  • axe-core. Zero violations across the variant × color × animation matrix.
  • Replace, don't overlay. When real data lands, swap the <Skeleton /> for the actual content — screen readers want the announcement transition (status → content). Don't paint the content under the skeleton.

Theming

ts
defineTheme({
  components: {
    Skeleton: {
      defaultProps: { animation: 'pulse', rounded: 'sm' },
      styleOverrides: {
        root: 'bg-bg-paper', // tighter contrast against page background
      },
    },
  },
});
defineTheme({
  components: {
    Skeleton: {
      defaultProps: { animation: 'pulse', rounded: 'sm' },
      styleOverrides: {
        root: 'bg-bg-paper', // tighter contrast against page background
      },
    },
  },
});

You can also retune the shimmer palette per theme by setting the two CSS variables the shimmer gradient references — set them anywhere from :root down to an element-level style:

css
:root {
  --sds-skeleton-base: rgba(0, 0, 0, 0.06);
  --sds-skeleton-highlight: rgba(255, 255, 255, 0.18);
}

:root[data-mode='dark'] {
  --sds-skeleton-base: rgba(255, 255, 255, 0.06);
  --sds-skeleton-highlight: rgba(255, 255, 255, 0.16);
}
:root {
  --sds-skeleton-base: rgba(0, 0, 0, 0.06);
  --sds-skeleton-highlight: rgba(255, 255, 255, 0.18);
}

:root[data-mode='dark'] {
  --sds-skeleton-base: rgba(255, 255, 255, 0.06);
  --sds-skeleton-highlight: rgba(255, 255, 255, 0.16);
}

Both have sensible RGB fallbacks baked into the gradient string, so the shimmer works out of the box on every theme — these overrides are pure refinement.

Props

PropTypeDefaultDescription
animationenum'shimmer'Animation style.
colorResponsiveValue<SkeletonColor>'neutral'Semantic palette role. Only visible when `variant="soft"`.
heightstring | number—CSS height. Number coerces to px.
roundedenum'md'Corner radius family.
sxSx—Theme-aware inline style object (resolves palette / spacing / radius tokens to CSS vars).
variantResponsiveValue<SkeletonVariant>'solid'Stylistic family.
widthstring | number—CSS width. Number coerces to px. Strings pass through (`'100%'`, `'24ch'`, etc.).