Documentation

Getting started, theming, accessibility, and full API reference.

Installation

AutoSkeleton ships as a single npm package with zero required dependencies (React 16.8+ is a peer dep).

bash
npm install @gyojiro/autoskeleton-react
# or
pnpm add @gyojiro/autoskeleton-react
# or
yarn add @gyojiro/autoskeleton-react

Setup

Import the bundled stylesheet once at the top level of your app. The CSS file contains the keyframe animations and CSS custom properties used internally.

Next.js App Router

tsx
// app/layout.tsx
import "@gyojiro/autoskeleton-react/style.css";
import type { Metadata } from "next";

export const metadata: Metadata = { title: "My App" };

export default function RootLayout({ children }: { children: React.ReactNode }) {
  return (
    <html lang="en">
      <body>{children}</body>
    </html>
  );
}

Vite / Create React App

tsx
// main.tsx (or index.tsx)
import "@gyojiro/autoskeleton-react/style.css";
import React from "react";
import ReactDOM from "react-dom/client";
import App from "./App";

ReactDOM.createRoot(document.getElementById("root")!).render(<App />);
If you use a global theme, optionally wrap your app once in SkeletonProvider — but it is completely optional. Every skeleton works standalone with sensible defaults.
Code snippets throughout these docs, /components, and /examples use Tailwind CSS classes (flex, gap-4, card, etc.) for the surrounding layout markup. The skeleton component calls themselves don't require Tailwind — they work with any styling setup — but if you copy a snippet wholesale and don't have Tailwind installed, the wrapper divs won't be laid out as shown; swap those classes for your own CSS, or add Tailwind to match exactly.

Quick Start

Import any component and drop it where the real content will go. No provider needed.

tsx
import {
  AvatarSkeleton,
  TextSkeleton,
  ButtonSkeleton,
  CardSkeleton,
} from "@gyojiro/autoskeleton-react";

// Inline composition
function UserCardSkeleton() {
  return (
    <div className="flex gap-3 p-4">
      <AvatarSkeleton size={48} />
      <div className="flex-1">
        <TextSkeleton lines={2} />
      </div>
    </div>
  );
}

// Or use a pre-built composite
function LoadingState() {
  return <CardSkeleton />;
}

Live preview

Real-World Pattern

The key to pixel-perfect skeleton UIs is to share the same container markup in both the loading and loaded states.

tsx
import { useState, useEffect } from "react";
import { AvatarSkeleton, TextSkeleton, ButtonSkeleton } from "@gyojiro/autoskeleton-react";

function UserProfile() {
  const [user, setUser] = useState<User | null>(null);

  useEffect(() => {
    fetchUser().then(setUser);
  }, []);

  // Shared card shell — used in BOTH branches
  const card = "flex flex-col gap-4 p-6 rounded-xl border bg-white";

  if (!user) {
    return (
      <div className={card}>
        <AvatarSkeleton size={64} />
        <TextSkeleton lines={3} />
        <ButtonSkeleton width="100%" height={40} />
      </div>
    );
  }

  return (
    <div className={card}>
      <img src={user.avatar} className="w-16 h-16 rounded-full" alt={user.name} />
      <div>
        <p className="font-semibold">{user.name}</p>
        <p className="text-sm text-slate-500">{user.bio}</p>
      </div>
      <button className="w-full h-10 rounded-lg bg-blue-600 text-white">Follow</button>
    </div>
  );
}
Match skeleton dimensions to real content: use <AvatarSkeleton size={64} /> when the real avatar is w-16 h-16 (64px), lineHeight={28} when the title is 1.75rem, and height={40} for a h-10 button. See the Examples page for full walkthroughs.

Theming

All theme values flow through React Context. Wrap once with SkeletonProvider to configure every skeleton below it, or pass theme props directly to SkeletonGroup for local overrides.

Default theme

tsx
const DEFAULT_THEME = {
  animation: "wave",           // "wave" | "pulse" | "fade" | "none"
  duration: 1.2,               // seconds
  easing: "ease-in-out",       // any CSS timing function
  animationDirection: "normal",// "normal" | "reverse" | "alternate" | "alternate-reverse"
  radius: "md",                // "none" | "sm" | "md" | "lg" | "full" | string
  color: "#E5E7EB",            // base background
  highlight: "#F9FAFB",        // shimmer highlight (wave animation)
};

SkeletonProvider

Pass any subset of theme props. Unspecified values fall back to the defaults above.

tsx
import { SkeletonProvider } from "@gyojiro/autoskeleton-react";

// Slower pulse instead of wave
<SkeletonProvider animation="pulse" duration={1.8}>
  <App />
</SkeletonProvider>

// Custom brand colors
<SkeletonProvider color="#E0E7FF" highlight="#EEF2FF">
  <App />
</SkeletonProvider>

// Reverse wave direction
<SkeletonProvider animationDirection="reverse">
  <App />
</SkeletonProvider>

// Cubic-bezier easing
<SkeletonProvider easing="cubic-bezier(0.4, 0, 0.2, 1)">
  <App />
</SkeletonProvider>

Animation types

ValueDescription
waveShimmer sweep from left to right (default)
pulseGentle opacity in / out pulse
fadeSoft fade in and out
noneStatic placeholder — no animation

animationDirection

Maps directly to the CSS animation-directionproperty. Useful for creating a “back and forth” shimmer effect.

tsx
// Default — shimmer left to right
<SkeletonProvider animationDirection="normal">…</SkeletonProvider>

// Shimmer right to left
<SkeletonProvider animationDirection="reverse">…</SkeletonProvider>

// Alternating — great for subtle pulse-like waves
<SkeletonProvider animationDirection="alternate">…</SkeletonProvider>
<SkeletonProvider animationDirection="alternate-reverse">…</SkeletonProvider>

CSS custom properties

You can also override theme values at the CSS level using these custom properties. This is useful for dark-mode overrides via a CSS media query.

css
/* globals.css */
:root {
  --skeleton-color: #E5E7EB;
  --skeleton-highlight: #F9FAFB;
  --skeleton-duration: 1.2s;
  --skeleton-easing: ease-in-out;
  --skeleton-direction: normal;
}

@media (prefers-color-scheme: dark) {
  :root {
    --skeleton-color: #374151;
    --skeleton-highlight: #4B5563;
  }
}

Dark Theme

The package exports a DARK_THEME preset that overrides the two color values to match dark backgrounds.

tsx
import { SkeletonProvider, DARK_THEME } from "@gyojiro/autoskeleton-react";

// DARK_THEME = { color: "#374151", highlight: "#4B5563" }

// Spread into SkeletonProvider
<SkeletonProvider {...DARK_THEME}>
  <ProfileSkeleton />
</SkeletonProvider>

// Conditionally apply based on app theme state
const { isDark } = useTheme();

<SkeletonProvider {...(isDark ? DARK_THEME : {})}>
  <App />
</SkeletonProvider>

Live dark / light toggle

Layout: Flex & Grid

SkeletonGroup arranges children with flexbox by default. A row next to a fixed-size element (like an avatar) fills the remaining space automatically — no manual flex: 1 needed.

tsx
import { SkeletonGroup, AvatarSkeleton, TextSkeleton } from "@gyojiro/autoskeleton-react";

<SkeletonGroup direction="row" gap={12} align="center">
  <AvatarSkeleton size={48} />
  <TextSkeleton lines={2} />
</SkeletonGroup>

Live preview

Grid

Set layout="grid" for CSS grid instead of flexbox. columns renders repeat(columns, 1fr) — that many equal-width tracks — or pass a raw grid-template-columns string for full control.

tsx
<SkeletonGroup layout="grid" columns={3} gap={16}>
  <Skeleton height={80} radius="md" />
  <Skeleton height={80} radius="md" />
  <Skeleton height={80} radius="md" />
</SkeletonGroup>

Responsive columns & direction

columns and direction both accept a { base, sm, md, lg, xl }object instead of a constant value, resolved via a CSS container query scoped to the group's own rendered width — not the viewport. A grid nested inside a narrow sidebar or modal responds to that container's width correctly, the same way it would at the edge of the browser window.

tsx
// 1 column by default, 2 from a 480px container width, 3 from 640px
<SkeletonGroup layout="grid" columns={{ base: 1, sm: 2, md: 3 }} gap={16}>
  {items.map((item) => <ProductCardSkeleton key={item.id} />)}
</SkeletonGroup>
Breakpoints are container-width, in pixels: sm = 480, md = 640, lg = 800, xl= 1024. Resize this browser window to see the grid above respond — it's reacting to its own container, not the page.

Local Overrides with SkeletonGroup

SkeletonGroup doubles as a layout wrapper and a local theme scope. Any theme props passed to it override only its descendants — the rest of the tree is unaffected.

tsx
import { SkeletonGroup, CardSkeleton, TextSkeleton } from "@gyojiro/autoskeleton-react";

// Global provider uses "wave"; this section uses "pulse"
<SkeletonProvider animation="wave">
  <TextSkeleton lines={3} />

  <SkeletonGroup animation="pulse" color="#DBEAFE" highlight="#EFF6FF">
    <CardSkeleton />
    <CardSkeleton />
  </SkeletonGroup>
</SkeletonProvider>

Live preview

Outer — wave (default)

SkeletonGroup override — pulse

Accessibility

AutoSkeleton follows WAI-ARIA guidelines for loading indicators.

aria-label

By default every skeleton is decorative (aria-hidden="true"). Pass an aria-label to expose it to screen readers with role="status".

tsx
// Decorative (default) — hidden from screen readers
<CardSkeleton />

// Announced — screen reader says "Loading product card..."
<CardSkeleton aria-label="Loading product card..." />

// Announce the whole section once instead of each piece
<div role="status" aria-label="Loading user profile...">
  <AvatarSkeleton />
  <TextSkeleton lines={2} />
</div>

aria-busy

SkeletonGroup renders aria-busy="true" by default when an aria-label is provided. Set it to false to suppress this.

tsx
<SkeletonGroup aria-label="Loading profile..." aria-busy={true}>
  <AvatarSkeleton />
  <TextSkeleton lines={3} />
</SkeletonGroup>

prefers-reduced-motion

The bundled stylesheet automatically disables all CSS animations when the user has requested reduced motion via the OS accessibility setting. No configuration needed — it works out of the box.

css
/* Already handled inside @gyojiro/autoskeleton-react/style.css */
@media (prefers-reduced-motion: reduce) {
  [data-skeleton] {
    animation: none;
  }
}
You can still use animation="none" programmatically on any skeleton to force a static placeholder regardless of user preference.

Best practices

  • Announce the loading region once using a wrapper <div role="status"> rather than on every individual skeleton.
  • Remove or hide the skeleton container (not just swap content) so screen readers are notified the loading state ended.
  • Use aria-labeltext that describes what is loading, not the visual shape (e.g. “Loading user profile” not “skeleton rectangle”).
  • Prefer animation="pulse" or animation="none" for content that will take a long time to load — the wave shimmer can feel distracting after a few seconds.

API Reference

Full props for every component are documented on the Components page with live previews, searchable props tables, and copy-paste code examples.

Exports

ExportKindDescription
SkeletonComponentCore primitive rectangle/circle block
SkeletonGroupComponentFlex layout wrapper + local theme scope
SkeletonProviderComponentGlobal theme context provider
TextSkeletonComponentMulti-line paragraph placeholder
AvatarSkeletonComponentCircular avatar placeholder
ButtonSkeletonComponentRounded button placeholder
ImageSkeletonComponentAspect-ratio-aware image placeholder
ArticleSkeletonComponentHero + author + body layout
CardSkeletonComponentVersatile card (column or row)
ChartSkeletonComponentBar, line, or donut chart placeholder
ChatMessageSkeletonComponentChat bubbles + input area
CommentSkeletonComponentStacked comment thread
DashboardSkeletonComponentStats + chart + table layout
FormSkeletonComponentLabeled fields + submit button
GallerySkeletonComponentCSS-grid image gallery
ListSkeletonComponentIcon + text list items
MediaObjectSkeletonComponentMedia block beside text
NavbarSkeletonComponentLogo + links + actions bar
PricingCardSkeletonComponentPricing tier card
ProductCardSkeletonComponentE-commerce product card
ProfileSkeletonComponentSocial profile layout
SidebarSkeletonComponentApp sidebar navigation
StatisticCardSkeletonComponentKPI / stat card
StoriesBarSkeletonComponentHorizontally-scrolling avatar row
TableSkeletonComponentTabular data placeholder
TimelineSkeletonComponentVertical timeline
DARK_THEMEConstant{ color: '#374151', highlight: '#4B5563' }
useSkeletonHookReturns the current SkeletonTheme from context
ResponsiveValue<T>TypeT | { base, sm, md, lg, xl } — for SkeletonGroup's columns/direction
SkeletonBreakpointType"sm" | "md" | "lg" | "xl"

useSkeleton hook

Reads the current SkeletonTheme from context — useful for building custom skeletons that respect the global theme.

tsx
import { useSkeleton } from "@gyojiro/autoskeleton-react";

function MyCustomSkeleton() {
  const theme = useSkeleton();
  // theme.color, theme.animation, theme.duration, etc.
  return (
    <div
      style={{
        width: 200,
        height: 20,
        background: theme.color,
        borderRadius: 4,
      }}
    />
  );
}

TypeScript types

tsx
import type {
  SkeletonTheme,           // Full theme config interface
  SkeletonAnimation,       // "wave" | "pulse" | "fade" | "none"
  SkeletonAnimationDirection, // "normal" | "reverse" | "alternate" | "alternate-reverse"
  SkeletonRadius,          // "none" | "sm" | "md" | "lg" | "full" | string
  SkeletonVariant,         // "default" | "rounded" | "circle"
} from "@gyojiro/autoskeleton-react";