# boneyard: skeleton screens generated from your real UI

> The boneyard-js framework captures bone data from a headless browser or a React Native device, then renders pixel-accurate placeholders at runtime. It is a build-time tool, not a runtime one, and that shapes who can use it.

**0xGF/boneyard** — Auto generated skeleton loading framework

- Repository: https://github.com/0xGF/boneyard
- Website: https://boneyard.vercel.app/
- Stars: 7,471 · Forks: 294
- Language: TypeScript
- License: MIT
- Published: 2026-09-22 · Updated: 2026-09-22 · Language: en
- Canonical page: https://hysenlabs.com/projects/0xgf-boneyard

## The problem boneyard-js solves

Hand-written skeleton screens drift. You build a card, write a placeholder that approximates its shape, then change the padding three weeks later and the skeleton no longer lines up. Nothing breaks, so nobody notices until a designer does.

boneyard-js takes the opposite approach. The README describes the goal as "pixel-perfect skeleton loading screens, extracted from your real UI. No manual measurement, no hand-tuned placeholders." Instead of you writing placeholder markup, a build step measures your actual components and writes a JSON file describing where the bones go.

The audience is frontend teams with a component library and more than a handful of loading states. A single-page marketing site does not need this; a dashboard with twenty list views does. The framework supports React, Preact, Vue, Svelte 5, Angular and React Native, and the README states all of them emit the same .bones.json format.

## How the capture pipeline works

There are two capture paths, and they are genuinely different mechanisms.

On the web, the CLI or the Vite plugin opens a headless browser, visits your running app, finds every component wrapped in <Skeleton name="...">, and snapshots their layout at multiple breakpoints. The default breakpoints are 375, 768 and 1280 pixels wide. The result is a set of .bones.json files, one per named skeleton.

On React Native, the flow inverts. The <Skeleton> component auto-scans in dev mode when the CLI is running. According to the README, it walks the fiber tree, measures views through UIManager, and sends bone data back to the CLI. The README also states there is zero overhead in production, which follows from the scan being gated on dev mode plus a running CLI.

At runtime, the <Skeleton> component reads the captured data and renders bones instead of children while loading is true. Bone positions are stored per breakpoint, so the same name can produce different layouts at 375 and 1280 pixels.

Two details matter for correctness. First, the CLI waits 800 ms after page load by default before capturing, controlled by the --wait flag. If your data has not resolved inside that window, the capture measures the wrong thing. Second, <BoneSuspense> exists for the Suspense case: the README says it renders the skeleton as the fallback at runtime while the CLI captures bones from the resolved children at build time, and that no initialData or placeholderData is required because the --wait window lets the query resolve naturally.

## Installing boneyard-js and capturing your first skeleton

Install the package from npm. The README gives this exact command:

```bash
npm install boneyard-js
```

Wrap a component in the React <Skeleton> and give it a unique name. The name determines the output filename, so name.bones.json is what gets written for name="blog-card".

```tsx
import { Skeleton } from 'boneyard-js/react'

function BlogPage() {
  const { data, isLoading } = useFetch('/api/post')
  return (
    <Skeleton name="blog-card" loading={isLoading}>
      {data && <BlogCard data={data} />}
    </Skeleton>
  )
}
```

Now run the capture. The CLI works with any framework, and the default output directory is ./src/bones.

```bash
npx boneyard-js build
```

Import the generated registry once in your app entry point. The README shows this as a single import:

```ts
import './bones/registry'
```

If you use Vite, the README recommends the plugin over the CLI because it removes the need for a second terminal. Bones are captured when the dev server starts and re-captured on every HMR update.

```ts
// vite.config.ts
import { boneyardPlugin } from 'boneyard-js/vite'

export default defineConfig({
  plugins: [boneyardPlugin()]
})
```

For React Native the command changes to npx boneyard-js build --native --out ./bones, and the README notes you open the app on device and bones capture automatically.

## Where the capture model breaks down

The biggest constraint is that capture needs a rendered component to measure. If your loading state is the first thing a user ever sees, because the route has no server-rendered markup and the query never resolves during the --wait window, the CLI has nothing to snapshot. The fixture prop exists for this: the README describes it as "mock content for CLI capture (dev only)". That means you write mock data anyway, which is part of the work the pitch implies you avoid.

The 800 ms default wait is a second failure mode. Slow local APIs, cold caches and heavy dev builds all push resolution past that window. Raising --wait helps, but the README does not document what happens when a capture times out or produces partial bones, and it does not document rollback of a bad capture. Treat stale .bones.json files as a real risk in a repository where several people work on the same components.

Third, the headless browser requirement is a CI constraint. The --cdp flag connects to an existing Chrome via debug port, which is useful locally, but the README does not describe a CI recipe. Teams that cannot run a browser in their pipeline will end up committing generated bones from a developer machine.

Finally, --no-scan skips filesystem route scanning, which implies the default scanner makes assumptions about your routing layout. Projects with unusual or fully dynamic routing should expect to pass a URL explicitly.

## boneyard-js compared with react-loading-skeleton

react-loading-skeleton is the obvious alternative for React teams, and the difference is where the shape comes from.

With react-loading-skeleton you compose placeholders by hand: a <Skeleton width={200} height={20} /> per line, arranged to approximate the real layout. It is a runtime-only library, it has no build step, no headless browser, no generated files, and it works in any environment where React renders. The cost is that the placeholder is a second implementation of your layout that you maintain separately.

boneyard-js inverts that trade. You write one wrapper, and the layout comes from measurement. You pay for it with a build pipeline, a dev-mode capture path, and generated artifacts that need to be regenerated when components change.

Neither is strictly better. If your loading states are simple and your layout is stable, hand-written placeholders are less machinery. If you have many list and card variants that change often, the measured approach removes a class of drift that hand-written skeletons invite. Note also that boneyard-js covers Preact, Vue, Svelte 5, Angular and React Native, while react-loading-skeleton is React-focused.

## Licence, maintenance and upgrade cost

The repository is MIT licensed, which permits commercial use, modification and redistribution with the licence text retained. That is a permissive default and imposes no copyleft obligation on your application code. This is not legal advice; check the LICENSE file in the repository for the exact terms.

The last push to the repository was on 2026-08-31, and v1.10.0 was released the same day. The release history shows v1.9.0 on 2026-07-07 and v1.8.1 on 2026-04-23, so the project is moving, but the cadence is uneven and the README does not describe a deprecation policy or a version compatibility matrix.

The practical upgrade cost sits in the generated artifacts. Because bones are build output, a version bump can change the JSON schema or the rendering, and you will not notice until you regenerate. Pin the version in package.json, regenerate bones after any upgrade, and diff the .bones.json files before committing. The monorepo uses pnpm 10.14.0 as its package manager, which is a detail about the repository itself, not about consuming the published package.

## Conclusion

Adopt boneyard-js if your app already renders meaningful layout during loading and you want placeholders that match your real components across breakpoints. Skip it if you cannot run a headless browser in CI, if your loading state has no resolved DOM to measure, or if you want a hand-written skeleton you control line by line. Before committing, verify that the CLI can reach your dev server, that your routes are discoverable by the filesystem scanner, and that the generated .bones.json files land in the directory your app imports from.

## FAQ

### Is boneyard-js based on a real UI or does it ship prebuilt skeletons?

It does not ship prebuilt skeletons. The CLI or Vite plugin visits your app, finds components wrapped in <Skeleton name="...">, and snapshots their layout at multiple breakpoints to produce .bones.json files.

### What is boneyard-js?

It is an auto-generated skeleton loading framework written in TypeScript and published under the MIT licence. It works with React, Preact, Vue, Svelte 5, Angular and React Native, and all of them output the same .bones.json format.

### How do I install and run boneyard-js?

Install it with npm install boneyard-js, wrap a component in <Skeleton name="..."> with a loading prop, run npx boneyard-js build to capture bones, and import the generated registry once in your app entry with import './bones/registry'.

### Does boneyard-js work with React Native?

Yes. Import Skeleton from boneyard-js/native and run npx boneyard-js build --native --out ./bones. The README states the component auto-scans in dev mode when the CLI is running, walks the fiber tree, measures views via UIManager, and adds zero overhead in production.

### What happens if the CLI captures before my data loads?

The CLI waits 800 ms after page load by default, set by the --wait flag. If your query cannot finish in that window, the README recommends passing a fixture, which is mock content used only for CLI capture in dev.

## Sources

- [0xGF/boneyard on GitHub](https://github.com/0xGF/boneyard)
- [License: MIT](https://github.com/0xGF/boneyard/blob/main/LICENSE)
- [Project website](https://boneyard.vercel.app/)
- [README](https://github.com/0xGF/boneyard/blob/main/README.md)
- [Releases](https://github.com/0xGF/boneyard/releases)

---

Hysen Labs editorial analysis, written from the project's own repository and release notes. Cite the canonical page: https://hysenlabs.com/projects/0xgf-boneyard
