Getting Started
Add skeleton loading screens to any React app in under 5 minutes.
npm install boneyard-jsAlso works with yarn add boneyard-js or pnpm add boneyard-js.
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>
)
}Give each skeleton a name — this is what the CLI uses to identify it and generate the bones file.
If your component needs data from an API or is behind authentication, add a fixtureprop with mock content. The CLI renders the fixture during capture — your real data isn't needed:
<Skeleton
name="blog-card"
loading={isLoading}
fixture={<BlogCard data={{
title: "Sample Post",
excerpt: "Placeholder text for layout...",
author: "Jane Doe"
}} />}
>
{data && <BlogCard data={data} />}
</Skeleton>Notes
- Next.js App Router:
<Skeleton>uses hooks — add"use client"to any file that imports it. - Data-dependent components: If your component needs API data to render, use the
fixtureprop to provide mock content for the build step. The fixture is only rendered duringnpx boneyard-js build— never in production.
With your dev server running, run the CLI. It visits your app at multiple viewport widths, snapshots every named <Skeleton>, and writes a .bones.json file for each one.
npx boneyard-js buildAuto-detects your running dev server by scanning common ports (3000, 5173, 4321, 8080…). Captures at 375px, 768px, and 1280px by default.
Playwright is included as a dependency. On first run you may need to install the browser: npx playwright install chromium
Apps with authentication
The build CLI uses a headless browser to visit your app. If your pages require login, you have a few options:
- Use the
fixtureprop — provide mock data so the CLI can capture bones without needing real user sessions. This is the recommended approach. - Run against a dev server with auth disabled — many frameworks support an environment flag to bypass auth in development.
- Point the CLI at public pages— if your login/onboarding pages don't require auth, capture those directly and use fixtures for authenticated pages.
The build generates a registry.js file alongside your bones. Add this one-line side-effect import to your app entry and every <Skeleton> auto-resolves its bones by name.
import './bones/registry'That's it. Your component stays clean — no per-file JSON imports needed:
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>
)
}boneyard picks the nearest breakpoint for the current viewport width. Re-run npx boneyard-js build any time your layout changes to regenerate.
Manual override
You can still pass initialBones directly if you prefer per-component imports. It takes precedence over the registry when provided.
The bones/ directory is auto-detected. If you have a src/ folder it goes to src/bones/, otherwise ./bones/. Override with --out.
Custom bone color
<Skeleton loading={isLoading} color="#c8b4f0">Static skeleton (no pulse animation)
<Skeleton loading={isLoading} animate={false}>Config file (build + runtime defaults)
{
"breakpoints": [390, 820, 1440],
"out": "./src/bones",
"color": "#e5e5e5",
"animate": "pulse"
}Create a boneyard.config.json in your project root. Controls both the CLI and runtime defaults. Works with all frameworks.
{
// Build options
"breakpoints": [375, 768, 1280],
"out": "./src/bones",
"wait": 800,
// Runtime defaults
"color": "#e5e5e5",
"darkColor": "#2a2a2a",
"animate": "shimmer",
"shimmerColor": "#ebebeb",
"darkShimmerColor": "#333333",
"speed": "2s",
"shimmerAngle": 110,
// Auth (for protected pages — web only)
"resolveEnvVars": true,
"auth": {
"cookies": [{ "name": "session", "value": "env[SESSION_TOKEN]" }],
"headers": { "Authorization": "Bearer env[API_TOKEN]" }
}
}| Key | Type | Default | Description |
|---|---|---|---|
| breakpoints | number[] | auto | Viewport widths (auto-detects Tailwind) |
| out | string | ./src/bones | Output directory |
| wait | number | 800 | ms to wait after page load |
| color | string | #f0f0f0 | Bone fill color (light mode) |
| darkColor | string | #222222 | Bone fill color (dark mode, .dark class) |
| animate | string | pulse | Animation: pulse, shimmer, or solid |
| shimmerColor | string | #f7f7f7 | Shimmer highlight color (light mode) |
| darkShimmerColor | string | #2c2c2c | Shimmer highlight color (dark mode) |
| speed | string | 2s / 1.8s | Animation duration (shimmer / pulse) |
| shimmerAngle | number | 110 | Shimmer gradient angle in degrees |
| auth | object | — | Cookies and headers for protected pages (web only) |
| resolveEnvVars | boolean | false | Replace env[VAR] in auth values |
CLI flags override config. Per-component props override runtime defaults.
Next steps
- See API Reference for all props and snapshot config options
- Try the Complex Example to see live bone extraction
- Browse Examples — blog cards, product grids, dashboards, chat threads