Skeleton screens are one of the best things you can do for perceived performance. But maintaining a separate set of hand-drawn rectangles and circles that mirror your real UI? That part is a grind — and it silently breaks every time your layout changes.
auto-skeleton fixes this. It measures your actual rendered DOM at runtime using getBoundingClientRect and generates pixel-accurate skeletons automatically. No shape definitions. No CLI snapshots. No config files. Just wrap your component and you're done.
The problem with existing solutions
Most skeleton libraries put the burden on you. You describe the skeleton — "three lines, one circle, two rectangles" — and hope it still matches your UI six sprints from now.
| Feature | Manual shapes | CLI snapshot tools | auto-skeleton |
|---|---|---|---|
| Zero config | ❌ | Partial | ✅ |
| No build step | ✅ | ❌ | ✅ |
| Always in sync with real UI | ❌ | Partial | ✅ |
| Adapts to dynamic content | ❌ | ❌ | ✅ |
| Cached for instant reloads | ❌ | ✅ | ✅ |
The core issue with manual approaches is that the skeleton is a second source of truth. auto-skeleton eliminates that entirely.
How it works
Rather than asking you to describe your UI twice, auto-skeleton reads it once — at runtime, on the real DOM.
- On the first non-loading render, the DOM inside
<AutoSkeleton>is scanned using aTreeWalker - Each visible element is measured with
getBoundingClientRectand classified as a rect, circle, or text block - Bone positions are stored in memory and
sessionStoragekeyed byid+ viewport width - When
loadingswitches back totrue, an absolutely-positioned overlay renders the cached bones instantly - The real content is hidden (
visibility: hidden) so layout is preserved during loading
React quick start
npm install @auto-skeleton/react
import { AutoSkeleton } from "@auto-skeleton/react";
import "@auto-skeleton/react/styles.css";
function UserProfile({ userId }) {
const { data, isLoading } = useUser(userId);
return (
<AutoSkeleton id="user-profile" loading={isLoading}>
<div className="profile">
<img src={data?.avatar} data-skeleton-shape="circle" />
<h2 data-skeleton-lines="1">{data?.name}</h2>
<p data-skeleton-lines="3">{data?.bio}</p>
</div>
</AutoSkeleton>
);
}
data-skeleton-shape="circle" tells the scanner to render the avatar as a circle. data-skeleton-lines="3" splits the paragraph into three stacked text lines. Both are optional — the scanner makes reasonable guesses without them.
Vue 3 quick start
npm install @auto-skeleton/vue
<script setup>
import { ref } from "vue";
import { AutoSkeleton } from "@auto-skeleton/vue";
import "@auto-skeleton/vue/styles.css";
const loading = ref(true);
</script>
<template>
<AutoSkeleton id="user-profile" :loading="loading">
<UserProfileCard />
</AutoSkeleton>
</template>
No changes required to UserProfileCard itself — the wrapper handles everything.
Lit / Web Components
npm install @auto-skeleton/lit lit
import "@auto-skeleton/lit";
<auto-skeleton skeleton-id="feed" .loading=${this.isLoading}>
<feed-list></feed-list>
</auto-skeleton>
Works natively in any framework — or no framework at all. The scanner crosses Shadow DOM boundaries automatically, so nested Lit components are discovered without extra configuration.
Options
| Option | Type | Default | Description |
|---|---|---|---|
animation |
`"wave" \ | "pulse" \ | "none"` |
debug |
boolean |
false |
Outline detected bones with dashed borders |
cache |
boolean |
true |
Cache measurements after first scan |
minSize |
number |
8 |
Minimum element size (px) to include |
watchDebounceMs |
number |
120 |
Debounce delay for layout change re-scans |
ignoreSelectors |
string[] |
[] |
CSS selectors for elements to skip |
Data attributes
Fine-tune individual elements without changing render logic:
| Attribute | Effect |
|---|---|
data-skeleton-ignore |
Skip this element entirely |
data-skeleton-shape="circle" |
Force a circular bone |
data-skeleton-lines="N" |
Split into N stacked text-line bars |
data-skeleton-container |
Scan children individually instead of as one block |
Theming
Two CSS custom properties control the entire look:
:root {
--as-base: #e4e4e7;
--as-highlight: rgba(255, 255, 255, 0.9);
}
.dark {
--as-base: #27272a;
--as-highlight: rgba(255, 255, 255, 0.05);
}
--as-base is the bone colour. --as-highlight is the shimmer that sweeps across during the wave animation.
Why this matters beyond convenience
The maintenance cost of manual skeletons is easy to underestimate. It's not just the initial setup — it's every subsequent layout change. Your designer widens the avatar. You add a badge. The bio gets a second paragraph. Each change requires you to remember to update the skeleton too. Most teams don't. The result is skeletons that subtly diverge from the real UI, undermining the perceived-performance benefit they were supposed to deliver.
auto-skeleton makes this a non-issue. The skeleton is derived from the real UI, so it stays correct by construction. You only intervene with data attributes when you want to fine-tune — not to keep things from drifting.
This also makes it significantly faster to add loading states to new components. You don't need to design the skeleton separately — you just wrap the component and move on.
Try it
- 🔴 Live demo → https://riazzahmedm.github.io/react-auto-skeleton/
- 📖 Docs → https://riazzahmedm.github.io/react-auto-skeleton/docs/getting-started/
- ⭐ GitHub → https://github.com/riazzahmedm/react-auto-skeleton
- 📦 npm →
@auto-skeleton/react·@auto-skeleton/vue·@auto-skeleton/lit
All packages are at v0.0.7. If you've been putting off skeleton loaders because of the setup cost — this is the week to change that. Drop a comment if you run into anything, and a ⭐ on GitHub goes a long way at this stage.
Top comments (0)