Agent
Feed this to Claude, ChatGPT, or any LLM to teach it boneyard.
12,131 chars
Also available at /agent/llms.txt for automated agent consumption.
llms.txt
# boneyard-js
Pixel-perfect skeleton loading screens, extracted directly from your real DOM. No manual measurement, no hand-tuned placeholders.
## How it works
1. Wrap your component with `<Skeleton>` and give it a `name`
2. Optionally add a `fixture` prop with mock data for the build step
3. Run `npx boneyard-js build` — it crawls your app, snapshots every named Skeleton, and writes `.bones.json` files + a `registry.js`
4. Add `import './bones/registry'` once in your app entry — every Skeleton auto-resolves its bones by name
## Install
```
npm install boneyard-js
```
## Quick start
```tsx
// app/layout.tsx — import the registry once (must be client-side for Next.js)
import './bones/registry'
```
```tsx
import { Skeleton } from 'boneyard-js/react'
function BlogPage() {
const { data, isLoading } = useFetch('/api/post')
return (
<Skeleton
name="blog-card"
loading={isLoading}
fixture={<BlogCard data={MOCK_DATA} />}
>
{data && <BlogCard data={data} />}
</Skeleton>
)
}
```
## The fixture prop
Apps often have authentication or user-specific data that isn't available during the build step. The `fixture` prop provides mock content that only renders when the CLI is capturing — never in production.
```tsx
<Skeleton
name="dashboard"
loading={isLoading}
fixture={<Dashboard data={{
title: "Sample Title",
stats: [{ label: "Revenue", value: "$12.3k" }]
}} />}
>
{data && <Dashboard data={data} />}
</Skeleton>
```
The mock data doesn't need to be real — it just needs to produce the same layout shape (same number of cards, similar text lengths, etc.).
## Generate the bones
With your dev server running:
```
npx boneyard-js build
```
The CLI:
- Auto-detects your dev server by scanning common ports (3000, 5173, 4321, 8080…)
- Auto-detects Tailwind breakpoints from your config (falls back to 375, 768, 1280)
- Crawls all internal links starting from the root URL
- Finds every `<Skeleton name="...">` on each page
- Captures bones at every breakpoint
- Writes `.bones.json` files + a `registry.js` to your output directory
- Auto-installs Chromium on first run
Or pass a URL explicitly: `npx boneyard-js build http://localhost:5173`
Re-run whenever your layout changes to regenerate. The CLI uses incremental builds — it hashes each skeleton's content and skips unchanged components. Use `--force` to bypass the cache and recapture everything.
**Next.js App Router:** The generated `registry.js` includes `"use client"` automatically. `<Skeleton>` uses hooks — add `"use client"` to any file that imports it.
## Excluding elements from capture
Add `data-no-skeleton` to any element you want to exclude from bone capture:
```tsx
<nav data-no-skeleton>
{/* No bone will be generated for this element */}
</nav>
```
**Note:** This only affects the capture/snapshot phase — excluded elements won't have bones drawn over them, but they are still hidden at runtime along with all other slot content (via `visibility: hidden`). To keep an element visible during loading, place it **outside** the `<Skeleton>` wrapper.
Or use `snapshotConfig` for more control:
```tsx
<Skeleton
snapshotConfig={{
excludeSelectors: ['.icon', '[data-no-skeleton]', 'svg'],
excludeTags: ['nav', 'footer'],
}}
>
```
## Dark mode
The component auto-detects dark mode via the `.dark` class on `<html>` or any parent element (standard Tailwind convention). It uses `darkColor` when dark mode is active. Does NOT use `prefers-color-scheme` — only the `.dark` class, giving the app developer explicit control.
Colors are best set in `boneyard.config.json`. Per-component overrides:
```tsx
<Skeleton color="#e5e5e5" darkColor="#2a2a2a" />
```
### Skeleton props
| Prop | Type | Default | Description |
|------|------|---------|-------------|
| loading | boolean | required | Show skeleton when true, real content when false |
| name | string | required | Unique name — the CLI uses this to generate the `.bones.json` file |
| fixture | ReactNode | — | Mock content rendered only during `npx boneyard-js build`. Never touches production |
| initialBones | ResponsiveBones | — | Optional manual override. If you use the registry, you don't need this |
| color | string | #f0f0f0 | Bone fill color for light mode |
| darkColor | string | #222222 | Bone fill color for dark mode (`.dark` class) |
| animate | "pulse" | "shimmer" | "solid" | "pulse" | Animation style (also accepts true/false) |
| className | string | — | Extra CSS class on the wrapper div |
| fallback | ReactNode | — | What to show if bones haven't been generated yet |
| snapshotConfig | SnapshotConfig | — | Control which elements are included/excluded during capture |
### snapshotConfig
| Option | Default | Description |
|--------|---------|-------------|
| excludeSelectors | [] | CSS selectors to skip (with all children) |
| excludeTags | [] | HTML tags to skip entirely |
| leafTags | p, h1–h6, li, tr | Tags treated as one solid block (merged with defaults) |
| captureRoundedBorders | true | Capture containers with border + border-radius as bones |
### npx boneyard-js build options
```
npx boneyard-js build [url] [options]
--out <dir> Output directory (default: ./src/bones)
--breakpoints <bp> Viewport widths, comma-separated (auto-detects Tailwind)
--wait <ms> Extra wait after page load (default: 800)
--force Recapture all (skip incremental cache)
--watch Re-capture when your app changes (listens for HMR)
--no-scan Skip filesystem route scanning (only crawl links)
--env-file <path> Load env vars from file (useful for Bun runtime)
--native React Native mode — scans from device (no browser)
```
## Bone format
Bones are stored as compact arrays: `[x, y, w, h, r]` with an optional 6th element `c` for container bones. `x` and `w` are percentages of container width. `y` and `h` are pixels. `r` is border radius (number or "50%"). The runtime also supports the legacy object format `{ x, y, w, h, r, c? }` for backwards compatibility.
## Low-level API (non-React)
```ts
import { snapshotBones } from 'boneyard-js'
const result = snapshotBones(document.querySelector('.card'))
import { renderBones } from 'boneyard-js'
const html = renderBones(result, '#d4d4d4')
container.innerHTML = html
// Manual bone registration (what the generated registry.js does automatically)
import { registerBones } from 'boneyard-js/react'
registerBones({ 'my-card': bonesJson })
```
## Authentication & protected routes
**Web (React/Svelte):** Configure auth in `boneyard.config.json`:
```json
{
"auth": {
"cookies": [{ "name": "session", "value": "env[SESSION_TOKEN]", "domain": "localhost" }],
"headers": { "Authorization": "Bearer env[API_TOKEN]" }
},
"resolveEnvVars": true
}
```
Or use the `fixture` prop to provide mock content that renders without auth.
**React Native:** Auth is a non-issue with `--native`. The app is already running on device with the user logged in — just open the screen you want to scan.
## React Native
```tsx
import { Skeleton } from 'boneyard-js/native'
<Skeleton name="profile" loading={isLoading}>
<ProfileCard />
</Skeleton>
```
Generate bones: `npx boneyard-js build --native --out ./bones`, then open your app on device. The Skeleton component auto-scans in dev mode — walks the React fiber tree, measures each view via UIManager, and sends bone data to the CLI. In production, scan code is completely inactive.
**Dynamic Type:** Always generate bones at default font scale (1.0). At runtime, boneyard renders children invisibly behind the skeleton overlay to measure the real content height, then scales bone positions proportionally via `scaleY`. This handles iOS Dynamic Type and Android font scaling automatically — no need to capture at multiple sizes.
After generating, add `import './bones/registry'` and reload the app.
## Preact
```tsx
import { Skeleton } from 'boneyard-js/preact'
import './bones/registry'
function App() {
const [loading, setLoading] = useState(true)
return (
<Skeleton name="card" loading={loading}>
<Card />
</Skeleton>
)
}
```
Native Preact integration — uses `preact/hooks` directly, no `preact/compat` needed. Same API as React. Same CLI: `npx boneyard-js build`. Works with the Vite plugin.
## Svelte
```svelte
<script>
import Skeleton from 'boneyard-js/svelte'
import '../bones/registry'
let loading = true
</script>
<Skeleton name="card" {loading}>
<Card />
</Skeleton>
```
Uses Svelte 5 snippets for `fallback` and `fixture`. Same CLI: `npx boneyard-js build`.
## Known limitations
- **Images**: Bone captures the bounding box — works even before the image loads
- **Dynamic content**: Bones reflect the layout at capture time. Re-run the build if layout changes
- **CSS transforms**: Bones use bounding rects, so transforms affect position but not bone sizing
- **React portals**: Elements outside the snapshot root aren't captured
- **Viewport vs container**: Breakpoints are based on viewport width, not container width
## Responsive
The CLI captures bones at multiple breakpoints (default: 375, 768, 1280). At runtime, `<Skeleton>` uses ResizeObserver to pick the closest match. Bones store `x` and `w` as percentages so they scale within a breakpoint range.
Custom breakpoints: `npx boneyard-js build --breakpoints 390,820,1440`
Tailwind breakpoints are auto-detected from your config.
## Config file
Create `boneyard.config.json` in your project root. This is the primary way to customize boneyard. Controls both the CLI build and runtime defaults for all `<Skeleton>` components:
```json
{
"breakpoints": [375, 640, 768, 1024, 1280, 1536],
"out": "./src/bones",
"wait": 800,
"color": "#e5e5e5",
"darkColor": "#2a2a2a",
"animate": "shimmer",
"shimmerColor": "#ebebeb",
"darkShimmerColor": "#333333",
"speed": "2s",
"shimmerAngle": 110
}
```
### Build-time options
| Key | Default | Description |
|-----|---------|-------------|
| breakpoints | [375, 768, 1280] | Viewport widths captured by CLI |
| out | ./src/bones | Output directory |
| wait | 800 | ms to wait after page load before capturing |
### Runtime options (baked into registry.js)
| Key | Default | Description |
|-----|---------|-------------|
| color | #f0f0f0 | Bone fill color (light mode) |
| darkColor | #222222 | Bone fill color (dark mode, `.dark` class) |
| animate | "pulse" | Animation: "pulse", "shimmer", or "solid" |
| shimmerColor | #f7f7f7 | Shimmer highlight color (light mode) |
| darkShimmerColor | #2c2c2c | Shimmer highlight color (dark mode) |
| speed | "2s" (shimmer) / "1.8s" (pulse) | Animation duration |
| shimmerAngle | 110 | Shimmer gradient angle in degrees |
| stagger | false | Delay between bones in ms (true = 80ms) |
| transition | false | Fade transition when loading ends in ms (true = 300ms) |
| boneClass | — | CSS class applied to each bone element |
Runtime options are automatically included in the generated `registry.js` via `configureBoneyard()`. Per-component props override config values.
## Package exports
- `boneyard-js` — snapshotBones, renderBones, fromElement
- `boneyard-js/react` — Skeleton, registerBones, configureBoneyard
- `boneyard-js/preact` — Skeleton, registerBones, configureBoneyard (native Preact, no compat needed)
- `boneyard-js/native` — Skeleton, registerBones, configureBoneyard (React Native)
- `boneyard-js/svelte` — Skeleton component, registerBones
- `boneyard-js/vue` — Skeleton component, registerBones, configureBoneyard
- `boneyard-js/angular` — SkeletonComponent, registerBones, configureBoneyard
- `boneyard-js/vite` — boneyardPlugin() Vite plugin for auto-capture
## Vite plugin
For Vite-based projects (Vue, Svelte, React with Vite), add the plugin to your vite.config.ts — no CLI needed:
```ts
import { boneyardPlugin } from 'boneyard-js/vite'
export default defineConfig({
plugins: [boneyardPlugin()]
})
```
Captures bones on dev server start and re-captures on every HMR update. Options: `out`, `breakpoints`, `wait`, `framework`, `skipInitial`.
CLAUDE.md: Drop this into your project's CLAUDE.md or .cursorrules file to give your AI assistant full context on boneyard.