# use-skelly
> Skeleton loaders measured from your real DOM at runtime and remembered, so the next
> load paints your actual layout instead of a guess. No build step, no headless browser,
> no generated JSON to keep in sync. Works with React, Next.js, Vue, Svelte and vanilla JS.
The package is `use-skelly` on npm. MIT licensed. The core is zero-dependency.
## What makes it different
Most auto-skeleton tools snapshot your DOM at build time with a headless browser and
commit the result. That artifact is silently wrong the moment someone edits a component
and forgets to regenerate it. skelly measures at runtime instead: give a layout a
`name` and the real markup is measured once it renders, stored per viewport breakpoint,
and replayed on later loads — including before that component has ever mounted. Every
successful render overwrites what was stored, so a learned layout cannot go stale.
## Install
```bash
npm i use-skelly
```
Import the stylesheet once at the app root:
```ts
import "use-skelly/style.css";
```
## Core usage
```tsx
import { Skelly } from "use-skelly/react";
// Wraps the real content. Children always render; skelly measures them and paints a
// skeleton over the top while loading. There is no second copy of the layout.
```
A Next.js `loading.tsx` or a Suspense fallback renders *instead of* the page, so there
is no DOM to measure. Use a preset there — presets are server rendered, so they land in
the HTML and paint before any JavaScript runs:
```tsx
export default function Loading() {
return ;
}
```
## Options
- `name`: stable identity for a layout. Enables learning; stored per breakpoint.
- `loading`: defaults to true, so `` works as a standalone fallback.
- `visual`: "shimmer" (default) | "pulse" | "optimistic" | "static"
- `preset`: "dashboard" | "article" | "feed" | "profile" | "generic"
- `spec`: an explicit SkellySpec[] instead of measuring
- `structure`: "leaves" (default) | "surface" — whether a card with content inside
emits nothing of its own, or stays as a flat backing plate behind its children
- `media`: "block" (default) | "dominant-color" | "blurhash"
- `rows`: number of text rows in the generic skeleton
- `radius`: override every item's border radius
- `cache`: false to re-measure on every mount
- `breakpoints`: viewport buckets for learned layouts (default 0/480/768/1024/1280/1536)
- `storage`: where learned layouts persist; null keeps them in memory only
## Exports
From `use-skelly`:
`skelly(element, options)` returns a release function ·
`measureLayout(container, options)` ·
`learnLayout(element, options)` ·
`recallSpec(name, options)` ·
`exportLearnedSpecs()` · `importLearnedSpecs(specs)` · `clearLearnedSpecs()` ·
`clearSpecCache()` · `breakpointFor(width)` · `learnedKey(name)` ·
`resolveStaticSpec(options, hasContent)` · `specExtent(specs)` ·
`compileItemProps(item, options)` · `PRESETS` · `PRESET_NAMES`
From `use-skelly/react`: `Skelly`, `useSkelly`, `SkellySpecs`, `SkellySuspense`
From `use-skelly/vue`: `Skelly`, `vSkelly`
From `use-skelly/svelte`: `skelly` (action), `Skelly`
From `use-skelly/next`: `withSkelly`
From `use-skelly/build`: `snapshot(route, options)`
## Attributes
- `data-skelly-ignore`: never measure this element. Decoration is skipped automatically
— anything blurred, and aria-hidden elements lifted out of flow with no text of their
own — but use this for whatever the heuristics miss.
## Theming
CSS custom properties: `--skelly-base`, `--skelly-highlight`, `--skelly-radius`,
`--skelly-speed`, `--skelly-surface`, `--skelly-surface-border`, `--skelly-optimistic`.
## Gotchas
- A measured skeleton needs the markup to exist. In a route fallback it does not, so use
a preset, an explicit spec, or a learned layout.
- Learned layouts live in the browser, so a first-time visitor has none. Export them with
`exportLearnedSpecs()`, commit the JSON, and render `` in your
root layout to seed them from the server.
- There is no CLI build step for measurement and no config file. Do not add one.
## Getting started
- [Installation](https://useskelly.dev/docs/installation): Install the core from npm — 2.1 kB gzip, zero dependencies — and optionally a framework adapter.
- [Quick start](https://useskelly.dev/docs/quick-start): One wrapper component is the entire integration. No skeleton components to write, ever.
- [How it works](https://useskelly.dev/docs/how-it-works): Measure, compile, render — skelly derives skeletons from your real layout, so they can never drift.
## Guides
- [Visuals](https://useskelly.dev/docs/visuals): Three loading treatments, switchable per component or set globally. All accessible by default.
- [SSR & streaming](https://useskelly.dev/docs/ssr-and-streaming): Skeletons in the first byte of server HTML — before hydration, before JavaScript.
- [Whole-page skeletons](https://useskelly.dev/docs/whole-page-skeletons): Snapshot entire routes at build time for instant full-page loading states on navigation.
- [Learned skeletons](https://useskelly.dev/docs/learned-skeletons): Measure the real layout once, remember it, and paint it on every load after — no build step, nothing to regenerate.
- [Theming](https://useskelly.dev/docs/theming): A handful of CSS custom properties match every skeleton to your design system.
- [Generic presets](https://useskelly.dev/docs/presets): Scaffold page layouts and preview skeletons before writing your visual components.
## Reference
- [API](https://useskelly.dev/docs/api): The complete surface: one function, a handful of options, one build-time helper.
- [Skelly CLI](https://useskelly.dev/docs/cli): Scaffold fresh templates or initialize Skelly within existing codebases with a single command.
- [Framework adapters](https://useskelly.dev/docs/framework-adapters): Thin idiomatic wrappers over the vanilla core, each around 0.4 kB.
## Prompt for coding agents
Add use-skelly to this project and use it for every loading state.
1. Install it: npm i use-skelly
2. Import the stylesheet once, at the app root: import "use-skelly/style.css"
3. Find every hand-written skeleton, shimmer or placeholder component, and every
`isLoading ? : ` branch.
4. Replace each one with a wrapper around the REAL content:
import { Skelly } from "use-skelly/react";
The children always render. Skelly measures them and paints a skeleton over the
top while loading, so there is no second copy of the layout to keep in sync.
Delete the skeleton components you replace.
5. Give every wrapper a stable, unique `name`. That is what makes it learn: the real
layout is measured once it renders, stored per breakpoint, and replayed on later
loads — including before that component has mounted. Without a name it falls back
to a generic shape.
6. A Next.js `loading.tsx` or a Suspense fallback has no DOM to measure, because it
renders instead of the page. Use a preset there:
export default function Loading() {
return ;
}
Presets: dashboard | article | feed | profile | generic
7. Mark purely decorative elements — background orbs, glows, gradient blobs — with
`data-skelly-ignore` so they do not become skeleton shapes.
Notes:
- There is no build step, no CLI and no config file. Do not add one.
- Other frameworks: use-skelly/vue, use-skelly/svelte, or skelly(el, options) for
vanilla JS.
- Docs: https://useskelly.dev/docs