Every React Native app needs a loading state. And every developer eventually ends up writing the same boilerplate — a bunch of gray View components with hardcoded sizes, trying to approximate what the real UI will look like.
I got tired of doing that on every project. So I built react-native-modern-shimmer — a lightweight, zero-dependency shimmer skeleton loader that works beautifully out of the box.
What it looks like
| Android | iOS |
|---|---|
![]() |
![]() |
![]() |
![]() |
Smooth, professional, and it automatically switches between light and dark mode — no extra props needed.
The problem with existing solutions
Most shimmer libraries for React Native have at least one of these problems:
- They require
expo-linear-gradientorreact-native-linear-gradient— which means native linking, which means it doesn't work in Expo Go - They don't support dark mode at all
- They have no TypeScript types
- They're abandoned and unmaintained
react-native-modern-shimmer solves all of these.
Installation
# npm
npm install react-native-modern-shimmer
# yarn
yarn add react-native-modern-shimmer
# expo
npx expo install react-native-modern-shimmer
No native modules. No linking. No extra packages. It works in Expo Go right out of the box.
Basic usage
import Shimmer from 'react-native-modern-shimmer';
export default function MyScreen() {
return (
<View style={{ padding: 16, gap: 12 }}>
<Shimmer width={200} height={16} />
<Shimmer width="80%" height={16} />
<Shimmer width="60%" height={16} />
</View>
);
}
That's it. No theme provider. No context. No configuration. It reads the system color scheme automatically using React Native's useColorScheme hook and updates instantly when the user switches themes.
Real-world examples
Card skeleton
<View style={{ gap: 12 }}>
<Shimmer width="100%" height={180} borderRadius={16} />
<Shimmer width="70%" height={16} borderRadius={6} />
<Shimmer width="50%" height={13} borderRadius={6} />
</View>
Avatar + name
<View style={{ flexDirection: 'row', alignItems: 'center', gap: 12 }}>
<Shimmer width={48} height={48} borderRadius={24} />
<View style={{ gap: 8 }}>
<Shimmer width={140} height={14} borderRadius={4} />
<Shimmer width={100} height={12} borderRadius={4} />
</View>
</View>
Product list rows
{Array.from({ length: 5 }).map((_, i) => (
<View key={i} style={{ flexDirection: 'row', alignItems: 'center', gap: 12, marginBottom: 16 }}>
<Shimmer width={52} height={52} borderRadius={12} />
<View style={{ flex: 1, gap: 8 }}>
<Shimmer width="80%" height={13} borderRadius={6} />
<Shimmer width="55%" height={13} borderRadius={6} />
</View>
</View>
))}
Full skeleton screen (with NativeWind / Uniwind)
import Shimmer from 'react-native-modern-shimmer';
export default function ShimmerScreen() {
return (
<SafeAreaView className="flex-1 bg-gray-100 dark:bg-zinc-950">
<ScrollView className="flex-1" contentContainerClassName="px-5 py-6 gap-5">
{/* Header */}
<View className="flex-row items-center justify-between">
<View className="flex-row items-center gap-3">
<Shimmer width={46} height={46} borderRadius={23} />
<View className="gap-2">
<Shimmer width={80} height={10} borderRadius={4} />
<Shimmer width={130} height={15} borderRadius={5} />
</View>
</View>
<Shimmer width={42} height={42} borderRadius={21} />
</View>
{/* Hero */}
<Shimmer width="100%" height={200} borderRadius={20} speed={1200} />
{/* Category chips */}
<View className="flex-row gap-2 flex-wrap">
{[64, 80, 72, 88].map((w, i) => (
<Shimmer key={i} width={w} height={32} borderRadius={16} speed={900 + i * 60} />
))}
</View>
{/* List */}
{[0, 1, 2, 3].map(i => (
<View key={i} className="flex-row items-center gap-4 p-4 rounded-2xl bg-white dark:bg-zinc-900">
<Shimmer width={54} height={54} borderRadius={16} speed={1000 + i * 70} />
<View className="flex-1 gap-2">
<Shimmer width="65%" height={13} borderRadius={4} />
<Shimmer width="45%" height={10} borderRadius={3} />
</View>
<Shimmer width={40} height={20} borderRadius={5} />
</View>
))}
</ScrollView>
</SafeAreaView>
);
}
All props
| Prop | Type | Default | Description |
|---|---|---|---|
width |
number or string
|
"100%" |
Accepts px number or "%" string |
height |
number or string
|
16 |
Accepts px number or "%" string |
borderRadius |
number |
8 |
Corner radius |
isDark |
boolean |
undefined |
Force dark or light. Omit to auto-detect |
baseColor |
string |
theme default | Override the background color |
speed |
number |
1000 |
Animation cycle in ms. Lower = faster |
style |
StyleProp<ViewStyle> |
undefined |
Extra styles on the container |
Why no LinearGradient?
The classic "sliding highlight" shimmer effect looks great but requires expo-linear-gradient or react-native-linear-gradient. Both need native linking or an Expo dev build — meaning they break in Expo Go and add setup friction.
This package achieves a professional shimmer using only React Native's built-in Animated API — a smooth opacity pulse that looks clean, feels native, and has zero setup cost. It runs on the native driver, so it's also battery friendly.
Links
- 📦 npm: react-native-modern-shimmer
- 🐙 GitHub: Tafsan-Mahmud/react-native-modern-shimmer
- 🙍🏼♂️ Linkedin: linkedin.com/in/abuhasnatnobin9
If this saved you time, a ⭐ on GitHub goes a long way. And if you run into any issues or have feature requests, open an issue — contributions are very welcome.




Top comments (0)