API Documentation
A deep-dive into react-adaptive-skeleton— how it works, how to configure it, and how to get the best performance out of it.
Wrap your UI in <AdaptiveSkeleton>. While loading, pass template data to your components so the real DOM structure is present and measurable.
The library recursively traverses the DOM inside the container. It measures every text node, image, and tagged element, capturing their exact position, size, and border radius relative to the container.
An absolutely-positioned overlay is drawn on top of the hidden content. Each rectangle is a clone of your skeleton template, sized and placed to pixel-perfectly match the real layout.
The API
The library exports a single factory function called createAdaptiveSkeleton. Use it to create a reusable skeleton component for your application. It requires a React Element that serves as your skeleton "template" and an optional configuration object.
Pro Tip: Create a Reusable ComponentWe recommend creating a single AdaptiveSkeleton component in your UI library and exporting it. This ensures consistent skeleton styling across your entire application.
Layout NoteThe returned component always wraps your content in a <div> (or your custom render element) with position: relative applied automatically so absolute-placed skeletons are positioned correctly. Keep this extra wrapper in mind when structuring flex/grid layouts — you may need to set an explicit className or style to control sizing.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
import React from "react";
import { createAdaptiveSkeleton } from "react-adaptive-skeleton";
// 1. Define your skeleton "stamp" - how each unit looks
const Template = () => (
<div className="bg-zinc-200 dark:bg-zinc-800 rounded animate-pulse w-full h-full" />
);
// 2. Create the reusable component with all available options
export const AdaptiveSkeleton = createAdaptiveSkeleton(<Template />, {
// Optional: Global selectors to always ignore
skipSelectors: [".no-sk", "#ignore-me"],
// Optional: Custom selectors to explicitly skeletonize
targetSelectors: [".skeletonize", "[data-sk]"],
});
// Use it anywhere: <AdaptiveSkeleton isLoading={true}>...</AdaptiveSkeleton>Default Adapted Tags
To provide a seamless experience out of the box, the library automatically identifies and skeletonizes:
Any other block elements (like div or section) require explicit skeletonization using selectors or attributes.
Component Props
skeletonClassName: string
Extra class name(s) appended to every rendered skeleton rectangle. Merged with the template element's own className, so the template's classes are always preserved.
skeletonStyle: CSSProperties
Extra inline styles merged into every rendered skeleton rectangle. Applied on top of the template element's own style, so individual properties can be overridden per-instance without redefining the entire template.
Configuration Options
skipSelectors: string[]
Any valid CSS query that the scanner should skip. This includes tag names ("label"), class names ( attribute selectors ("[data-ignore='true']").
skipTextSelectors: string[]
CSS selectors for elements whose text nodes should be ignored. This is useful when you want to skeletonize an element's structure but keep its text visible or untouched.
targetSelectors: string[]
Explicit CSS selectors, tag names, or attribute selectors defining which elements should be skeletonized. This accepts any valid CSS query.
classNameMerger: (...classes: string[]) => string
Custom function used to merge the skeleton template's className with the instance-level skeletonClassName prop. Pass twMerge from tailwind-merge to avoid Tailwind class conflicts (e.g. two bg-* utilities colliding). When omitted, classes are joined with a plain space.
defaultProps: HTMLAttributes & Record<string, unknown>
Default HTML props applied to the wrapper <div> for every instance of this skeleton. Instance-level props (className, style, data attributes, etc.) override these defaults. Useful for adding persistent utility classes (e.g. group/skeleton for Tailwind group variants) or data attributes without repeating them on every usage.
Attribute Selectors
You can use data attributes to precisely control how the scanner handles specific nodes.
data-skeletonForces the scanner to treat the element as a block.
data-no-skeletonTells the scanner to ignore this element and all its children.
data-no-skeleton-textTells the scanner to ignore the text nodes within this element and its descendants, while still processing other child elements.
data-flat-skeletonForces the skeleton to be flat. By default, skeletons automatically adapt to an element's custom border-radius (like rounded-2xl) or fallback to the skeletonTemplate's radius for sharp elements.
Hot Swapping Data
For react-adaptive-skeleton to know what shapes to draw, the underlying DOM structures must be rendered. Feeding "template data" to your UI components while loading isn't just about avoiding a React crash—it's how the library measures the physical dimensions, margins, and layouts to generate perfectly matching skeletons!
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
// Define safe fallbacks that represent the visual space you need
const dummyUser = {
name: "Placeholder Name",
role: "Admin"
};
function UserProfile() {
const { data, isLoading } = useUserQuery();
const userData = isLoading ? dummyUser : data;
return (
<AdaptiveSkeleton isLoading={isLoading}>
{/* The component renders this structure off-screen to measure bounds. */}
<div className="flex gap-4 p-4 border rounded-xl">
{/* Target this specific div for skeletonizing */}
<div className="size-12 relative aspect-video"
// target this node for skeleton
data-skeleton
// makes skeleton corners flat ignoring templates border radii
data-flat-skeleton
>
<Image src={userData.avatar} fill alt={userData.name} />
</div>
<div className="flex-1 space-y-2">
<h3 className="font-bold">{userData.name}</h3>
{/* Skip skeletonizing this metadata */}
<p className="text-sm text-zinc-500" data-no-skeleton>
{userData.role}
</p>
</div>
</div>
</AdaptiveSkeleton>
);
Polymorphic Containers
AdaptiveSkeleton is polymorphic by nature, meaning it can render as any HTML element using the as prop. This is particularly useful in situations like table rows, where strict HTML nesting rules must be followed.
Efficiency TipWrap the entire tbody to minimize the number of observers. The library will scan all rows and cells within that single container.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
function UserTable({ users, isLoading }) {
return (
<table className="w-full min-w-120 text-left border-collapse">
<thead>
<tr>
<th className="p-2 border-b">Name</th>
<th className="p-2 border-b">Role</th>
<th className="p-2 border-b">Address</th>
</tr>
</thead>
{/* Wrap the entire tbody — render prop keeps valid HTML structure */}
<AdaptiveSkeleton render={<tbody />} isLoading={isLoading}>
{users.map((user) => (
<tr key={user.id}>
<td className="p-2">
<span data-skeleton>{user.name}</span>
</td>
<td className="p-2 border-b">{user.role}</td>
<td className="p-2 border-b">{user.address}</td>
</tr>
))}
</AdaptiveSkeleton>
</table>
);
}Scroll Containers
Because skeleton rectangles are position: absolute overlays placed relative to the AdaptiveSkeleton container, they will not scroll with the content if you put the component outside of a scrollable element. This causes the skeletons to "float" or overflow weirdly when the user scrolls.
Rule of ThumbAlways place the <AdaptiveSkeleton> inside the scroll container, wrapping the scrolling content directly. Make sure the scroll container itself has a defined dimension and position: relative if necessary.
1
2
3
4
5
6
7
8
9
10
11
12
13
// ❌ WRONG: Skeletons will float over the scroll view
<AdaptiveSkeleton isLoading={true}>
<div className="h-96 overflow-y-auto">
{/* scrolling content */}
</div>
</AdaptiveSkeleton>
// ✅ CORRECT: The overlay will scroll smoothly with the content
<div className="h-96 overflow-y-auto">
<AdaptiveSkeleton isLoading={true}>
{/* scrolling content */}
</AdaptiveSkeleton>
</div>Performance Analysis
Choosing the right placement for your AdaptiveSkeleton container is key to maintaining a smooth 60fps experience. Depending on your UI complexity, you can choose between a Macro (Single Container) or Micro (Per Card) approach.
Single Container (Macro)
- + Lowest memory overhead (1 observer)
- + Simplest implementation
- - Full rescan on any change
Best For
Static lists or simple layouts where cards don't resize independently.
Per Card (Micro)
- + Prevents long-tasks on main thread
- + Precise internal resize detection
- - Higher overhead (N observers)
Best For
Interactive cards, complex layouts, or large dynamic feeds.