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" &#124; "shimmer" &#124; "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.