React Native

Use boneyard in React Native and Expo apps. Same pixel-perfect skeletons, same bone format — native renderer with built-in device scanning.

Quick start

1. Install

bash
npm install boneyard-js

2. Wrap your components

tsx
import { Skeleton } from 'boneyard-js/native'

function ProfileScreen() {
  const [loading, setLoading] = useState(true)
  return (
    <Skeleton name="profile-card" loading={loading}>
      <ProfileCard />
    </Skeleton>
  )
}

3. Generate bones

bash
npx boneyard-js build --native --out ./bones

Then open your app on device or simulator. Bones are captured automatically.

4. Import the registry and reload

tsx
// Add once in your app entry (e.g. App.tsx)
import './bones/registry'

Reload your app after adding the import — the skeletons will render on the next launch.

How native scanning works

On web, the CLI opens a headless browser and snapshots the DOM. On React Native, the <Skeleton> component itself does the scanning — no browser needed.

# Terminal — start the capture listener
npx boneyard-js build --native --out ./bones
# Open your app on device or simulator
# Bones appear in the CLI as they're captured
# Press Ctrl+C when done

What happens under the hood

  1. The CLI starts a listener on port 9999 and waits for bone data.
  2. In dev mode, every <Skeleton name="..."> checks if the CLI is listening.
  3. If found, it walks the React fiber tree, measures each native view's position with UIManager.measureLayout, and sends the results.
  4. The CLI writes .bones.json files + a registry.js that imports from boneyard-js/native.

Scanning happens once per named skeleton, 800ms after mount. In production builds (__DEV__ === false) the scan code is completely inactive — zero overhead.

Reload after generating bones

After running build --native, you need to reload your app for the new bone data to take effect. The registry is imported at startup — a hot reload won't pick up new .bones.jsonfiles. Shake your device and tap "Reload", or press r in the Expo terminal.

Other ways to generate bones

The --native flag is the easiest way. But if you need alternatives:

Expo web

If your app supports Expo web, you can use the standard web CLI. Create a capture page with the web Skeleton, run npx expo start --web, then npx boneyard-js build http://localhost:8081 --native. The --native flag ensures the registry imports from boneyard-js/native.

Copy from a web project

If you already have a web version with boneyard, just copy the .bones.json files. The format is identical across platforms.

bash
cp src/bones/*.bones.json ../my-rn-app/bones/

Write by hand

Bones are compact tuples: [x, y, w, h, r, c?].x/w are % of container width,y/h are pixels,r is border radius,c marks container bones.

bones/card.bones.json
{
  "breakpoints": {
    "375": {
      "name": "card",
      "viewportWidth": 375,
      "width": 343,
      "height": 200,
      "bones": [
        [0, 0, 100, 200, 12, true],
        [4, 16, 18, 64, "50%"],
        [26, 20, 45, 18, 6]
      ]
    }
  }
}
Props
PropTypeDefaultDescription
loadingbooleanShow skeleton or children
namestringResolve bones from registry by name (also used for scanning)
initialBonesResponsiveBonesPass bones directly (takes precedence over registry)
colorstring#d4d4d4Bone color in light mode
darkColorstring#3a3a3cBone color in dark mode
darkbooleanautoForce dark/light mode (defaults to system scheme)
animatebooleantrueEnable pulse animation
styleViewStyleAdditional style for the container
fallbackReactNodeShown when loading but no bones are available
Dark mode

Auto-detects via useColorScheme(). Override with the dark prop if your app manages its own theme:

tsx
// Force light mode regardless of system theme
<Skeleton loading={loading} dark={false}>

// Force dark mode
<Skeleton loading={loading} dark={true}>

// Custom dark mode colors
<Skeleton loading={loading} dark darkColor="#1e1e2e">
Expo & Metro setup
RequirementMinimum version
Expo SDK50+ (Fabric/New Architecture enabled by default)
React Native (bare)0.71+ (works with both Paper and Fabric)

Import from boneyard-js/native (not boneyard-js/react-native). Metro has special handling for paths containing "react-native" which can cause resolution issues.

If you link the package locally during development, add the source directory to Metro's watch folders:

metro.config.js
const { getDefaultConfig } = require('expo/metro-config')
const path = require('path')

const config = getDefaultConfig(__dirname)

// Only needed for local symlinked development
const boneyardPath = path.resolve(__dirname, '../boneyard/packages/boneyard')
config.watchFolders = [boneyardPath]
config.resolver.nodeModulesPaths = [
  path.resolve(__dirname, 'node_modules'),
  path.resolve(boneyardPath, 'node_modules'),
]

module.exports = config

When using boneyard-js from npm (not a local symlink), no Metro config changes are needed.

Project structure
my-rn-app/
├── bones/
├── profile-card.bones.json
├── feed-item.bones.json
└── registry.js ← auto-generated
├── components/
├── ProfileCard.tsx
└── FeedItem.tsx
├── App.tsx ← import './bones/registry'
├── app.json
└── package.json
Differences from web
FeatureWebReact Native
Importboneyard-js/reactboneyard-js/native
CLInpx boneyard-js buildnpx boneyard-js build --native
How it scansHeadless browser (Playwright)Fiber tree + UIManager on device
AnimationCSS keyframesAnimated API (opacity pulse)
Dark modeprefers-color-scheme + .dark classuseColorScheme() + dark prop
ResponsiveResizeObserver on containeruseWindowDimensions (screen width)
Bone formatIdentical .bones.json — fully cross-platform
Auth-protected screens

With --native, auth is a non-issue. Your app is already running on the device with the user logged in — the Skeleton component scans whatever is currently rendered. There are no cookies or headers to configure because no browser is involved.

The auth config in boneyard.config.json (cookies, headers) is only for the web CLI path where a headless browser needs to access protected pages. With --native, the device handles auth natively — just open the screen you want to scan.

Next steps

  • See Getting Started for the full web setup walkthrough
  • See Output to understand the .bones.json format in detail
  • Browse Examples for bone data you can use in your RN app