npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

react-zero-skeleton

v0.8.0

Published

Stop writing skeleton loaders. Auto-generated from your component layout.

Readme

react-zero-skeleton

Stop writing skeleton loaders.

npm version npm downloads bundle size TypeScript MIT License

→ Live demo & docs

Wrap your component. Pass two props. react-zero-skeleton measures the real layout and generates one bone per element : automatically, always in sync. Works with React Native and React (web).


The Problem

Every skeleton library makes you write the component twice:

// 1. Your real component
function ArticleCard({ article }) {
  return (
    <View>
      <Image source={{ uri: article.cover }} style={{ height: 160 }} />
      <Text style={{ fontSize: 16 }}>{article.title}</Text>
      <Text style={{ color: '#888' }}>{article.excerpt}</Text>
    </View>
  )
}

// 2. A skeleton copy you maintain forever
const ArticleCardSkeleton = () => (
  <SkeletonPlaceholder>
    <View style={{ height: 160 }} />
    <View style={{ width: '80%', height: 18 }} />
    <View style={{ width: '60%', height: 14 }} />
  </SkeletonPlaceholder>
)

// Design changes → you update one, forget the other → they drift apart.

The Solution

import { withSkeleton } from 'react-zero-skeleton'

// Write your component once : no skeleton needed
function ArticleCard({ article }) {
  return (
    <View>
      <Image source={{ uri: article.cover }} style={{ height: 160 }} />
      <Text style={{ fontSize: 16 }}>{article.title}</Text>
      <Text style={{ color: '#888' }}>{article.excerpt}</Text>
    </View>
  )
}

export default withSkeleton(ArticleCard)

// Two props wherever you use it
<ArticleCard hasSkeleton isLoading={isLoading} article={data} />

react-zero-skeleton renders your component invisibly, measures every element, and generates a matching bone for each one. Layout changes automatically.


Installation

npm install react-zero-skeleton
# or
yarn add react-zero-skeleton

No native code. No pod install. No linking.

The bundler picks the right version automatically:

  • React Native / Metro → native build (Fiber + onLayout + Animated)
  • React / Web (Next.js, Vite…) → web build (DOM + ResizeObserver + CSS animations)

Quick Start

1 : Wrap your component

// ArticleCard.tsx
import { withSkeleton } from 'react-zero-skeleton'

function ArticleCard({ article }) {
  return (
    <View>
      <Image source={{ uri: article.cover }} style={{ height: 160 }} />
      <Text style={{ fontSize: 16 }}>{article.title}</Text>
      <Text style={{ color: '#888' }}>{article.excerpt}</Text>
    </View>
  )
}

export default withSkeleton(ArticleCard)

2 : Use it

// Two props. That's it.
<ArticleCard hasSkeleton isLoading={isLoading} article={data} />

// Shorthand : activates hasSkeleton AND isLoading at once
<ArticleCard isLoadingSkeleton article={data} />

3 : (Optional) Global theme

import { SkeletonTheme } from 'react-zero-skeleton'

export default function App() {
  return (
    <SkeletonTheme animation="wave" color="#E0E0E0">
      <YourApp />
    </SkeletonTheme>
  )
}

Animations

| Animation | Description | | --------- | ----------- | | pulse | Soft opacity fade. The default. | | wave | Shimmer that slides left to right. | | shiver | Intense wave : wider amplitude, faster speed. | | shatter | Grid fragmentation : squares fade in/out with stagger. | | none | Static placeholder. Useful for reduced-motion. |

// Global via SkeletonTheme
<SkeletonTheme animation="wave">
  <App />
</SkeletonTheme>

// Per component
<ArticleCard
  hasSkeleton
  isLoading={isLoading}
  skeletonConfig={{ animation: 'shatter' }}
/>

// Speed control
skeletonConfig={{ animation: 'wave', speed: 'slow' }}   // 0.5×
skeletonConfig={{ animation: 'wave', speed: 'rapid' }}  // 2×
skeletonConfig={{ animation: 'wave', speed: 1.5 }}      // custom multiplier

wave / shiver on React Native

wave and shiver require a LinearGradient peer on React Native. Install one:

# Expo
npx expo install expo-linear-gradient

# Bare React Native
npm install react-native-linear-gradient

Both are detected automatically. No extra config needed.

On React (web), wave and shiver use CSS gradients : no peer dependency required.


Shatter

Each bone is subdivided into a grid of squares that fade in/out with staggered delays.

<ArticleCard
  hasSkeleton
  isLoading={isLoading}
  skeletonConfig={{
    animation: 'shatter',
    shatterConfig: {
      gridSize: 6,        // columns in the grid
      stagger: 80,        // ms delay between squares
      fadeStyle: 'radial' // 'random' | 'cascade' | 'radial'
    }
  }}
/>

API Reference

Props injected by withSkeleton

| Prop | Type | Description | |------|------|-------------| | hasSkeleton | boolean | Activates skeleton on this component | | isLoading | boolean | Shows the skeleton when true | | isLoadingSkeleton | boolean | Shorthand : activates hasSkeleton + isLoading | | skeletonConfig | SkeletonConfig | Local config override (highest priority) |

Config priority: skeletonConfig prop > SkeletonTheme > defaults.


SkeletonConfig

| Prop | Type | Default | Description | | ---- | ---- | ------- | ----------- | | animation | 'pulse' \| 'wave' \| 'shiver' \| 'shatter' \| 'none' | 'pulse' | Animation mode | | color | string | '#E0E0E0' | Base bone color | | highlightColor | string | '#F5F5F5' | Highlight color for wave / shiver | | speed | 'slow' \| 'normal' \| 'rapid' \| number | 'normal' | Animation speed | | borderRadius | number | 4 | Fallback corner radius | | direction | 'ltr' \| 'rtl' | 'ltr' | Shimmer direction | | minDuration | number | 0 | Minimum ms the skeleton stays visible | | disabled | boolean | false | Never show skeleton if true | | maxBonesInList | number | 0 | Max bones rendered in FlatList (0 = unlimited) | | shatterConfig | ShatterConfig | see below | Shatter animation config | | imageConfig | { aspectRatio: number } | { aspectRatio: 1 } | Image fallback dimensions |

Since v0.3, borderRadius is read from each element's StyleSheet style automatically. config.borderRadius acts as the fallback when the element has no explicit radius.

withSkeleton options

Second argument : withSkeleton(Component, options?):

| Option | Type | Default | Description | | ------ | ---- | ------- | ----------- | | measureStrategy | 'auto' \| 'root-only' | 'auto' | 'auto' walks the Fiber tree (one bone per element); 'root-only' restores v0.2 single-block behaviour | | maxDepth | number | 8 | Max depth of the Fiber tree traversal | | exclude | string[] | [] | Component displayNames excluded from the fiber walk (produce no bones) | | mockProps | Record<string, unknown> | {} | Props used for the invisible warmup render on cold start : see below |

export default withSkeleton(Screen, { exclude: ['MapView', 'VideoPlayer'] })
export default withSkeleton(Screen, { measureStrategy: 'root-only' })

mockProps : cold start

On first load, real props often carry no data (article: null) so the component renders nothing and no layout can be measured. mockProps provides fake data for the invisible warmup render so the fiber walker always has a realistic layout to measure:

withSkeleton(ArticleCard, {
  mockProps: { article: { title: 'Lorem ipsum', image: null } }
})

The mock data is merged on top of the real props ({ ...componentProps, ...mockProps }) and used only while isLayoutCaptured is false. Once the real layout is captured it is never used again.

For FlatList, combine with placeholder items on the list side:

const data = isLoading ? Array(5).fill(null) : realData

<FlatList
  data={data}
  renderItem={({ item }) => (
    <ArticleCard article={item} hasSkeleton isLoading={item === null} />
  )}
/>

registerSkeletonLeaf

Registers additional component names as skeleton leaf elements. Use this for custom image libraries that are not detected automatically.

import { registerSkeletonLeaf } from 'skelter'
registerSkeletonLeaf('FastImage', 'ExpoImage')

ShatterConfig

| Prop | Type | Default | Description | |------|------|---------|-------------| | gridSize | number | 6 | Number of columns | | stagger | number | 80 | Delay in ms between each square | | fadeStyle | 'random' \| 'cascade' \| 'radial' | 'random' | Square fade order |

SkeletonTheme props

All SkeletonConfig props, plus:

| Prop | Type | Default | Description | |------|------|---------|-------------| | children | ReactNode | : | Your app |


React Native : additional options

The following are available in the React Native build only.

withSkeleton(Component, options?)

| Option | Type | Default | Description | |--------|------|---------|-------------| | measureStrategy | 'auto' \| 'root-only' | 'auto' | 'auto' = one bone per element (Fiber walk); 'root-only' = single root block | | maxDepth | number | 8 | Max depth of the Fiber tree traversal | | exclude | string[] | [] | Component displayNames to skip during Fiber walk |

// Opt out of Fiber walk for a heavy screen
export default withSkeleton(Screen, { measureStrategy: 'root-only' })

// Exclude a third-party widget
export default withSkeleton(Screen, { exclude: ['MapView', 'VideoPlayer'] })

registerSkeletonLeaf

Register custom image libraries as leaf elements in the Fiber walk.

import { registerSkeletonLeaf } from 'react-zero-skeleton'

// Call once before your first render
registerSkeletonLeaf('FastImage', 'ExpoImage')

SkeletonTheme : auto mode (React Native only)

Injects hasSkeleton on all children automatically via React.cloneElement.

<SkeletonTheme
  animation="wave"
  auto
  exclude={['MapView', 'NavigationContainer']}
>
  <App />
</SkeletonTheme>

Then anywhere in the tree, just pass isLoading:

<ArticleCard isLoading={isLoading} />

Use exclude to protect third-party components that reject unknown props.


Limitations

React Native : Fiber walk reads React internals

Per-element measurement reads _reactInternals / _reactFiber from native View instances. These are undocumented React internals, stable across React 17-18. If the walk fails, react-zero-skeleton falls back to a single root bone automatically.

React Native : wave / shiver need a gradient peer

Without expo-linear-gradient or react-native-linear-gradient, these animations fall back to a solid placeholder. See wave / shiver on React Native.

React Native : shatter falls back to pulse in FlatList

Inside FlatList / FlashList, shatter automatically falls back to pulse for performance. This is silent and intentional.

React Native : animations run on the JS thread

All RN animations use the Animated API. On low-end devices with long lists, you may see frame drops. Reanimated worklets are on the roadmap.


Comparison

| Feature | react-zero-skeleton | react-native-auto-skeleton | react-content-loader | react-loading-skeleton | |---------|:-----------------------:|:--------------------------:|:--------------------:|:----------------------:| | Zero config | ✅ | ✅ | ❌ | ❌ | | Auto-generated from layout | ✅ | ✅ | ❌ | ❌ | | React web support | ✅ | ❌ | ✅ | ✅ | | Shatter animation | ✅ | ❌ | ❌ | ❌ | | No native code | ✅ | ❌ | ✅ | ✅ | | RTL support | ✅ | ❌ | ❌ | ✅ | | Cache aware | ✅ | ❌ | ❌ | ❌ |

¹ One bone per element by default (v0.3+). measureStrategy: 'root-only' falls back to one block per component root. ² Via cloneElement injection : may generate warnings on some third-party components.


Roadmap

v0.3 : Current

  • ✅ Per-element bones : one bone per View / Image / Text, auto-measured from Fiber tree
  • ✅ Per-element borderRadius : read from each element's StyleSheet
  • withSkeleton(Component, options?) : measureStrategy, maxDepth, exclude, mockProps
  • mockProps : cold start solver, warmup renders with fake data before real data arrives
  • registerSkeletonLeaf : add custom image components to the leaf registry
  • ✅ FlatList auto-detection : switches to root-only inside VirtualizedList
  • pulse, wave, shiver, shatter animations, AnimationSpeed presets
  • ✅ FlatList optimization, SSR safe, cache aware, RTL, accessibility (reduce motion)

v1 : Future

  • Opt-in Suspense API : <SkeletonSuspense fallback={<SkeletonOf component={X} />}>
  • Dark mode auto via useColorScheme
  • Static codegen for FlashList items


Contributing

npm install
npm run build
npm test
npm run typecheck

Open a PR against main. Please include a changeset:

npx changeset

License

MIT © J-Ben