Tailwind in React Native: Once You Start, You Can't Go Back
Introduction
I resisted Tailwind CSS for a long time. Writing className="flex items-center justify-between px-4 py-2" felt wrong compared to clean, semantic CSS. Then I tried NativeWind—Tailwind for React Native—and everything changed.
It took a few days to get comfortable. But once I got used to it, going back to StyleSheet felt painful. Now I use NativeWind for every React Native project, alongside tools like Jotai for state management and Maestro for testing.
Let me show you the best way to set it up, especially for dark mode.
The Problem with StyleSheet
React Native's StyleSheet works, but it gets messy fast:
const styles = StyleSheet.create({
container: {
flex: 1,
backgroundColor: '#F5F5F5',
padding: 16,
},
card: {
backgroundColor: '#FFFFFF',
borderRadius: 8,
padding: 12,
marginBottom: 16,
},
text: {
fontSize: 16,
color: '#212121',
},
});
<View style={styles.container}>
<View style={styles.card}>
<Text style={styles.text}>Hello</Text>
</View>
</View>
Now add dark mode. You need to check theme everywhere, create conditional styles, and manage two sets of colors. It becomes a mess quickly.
Enter NativeWind
NativeWind brings Tailwind's utility classes to React Native. Same syntax, same classes, just works.
<View className="flex-1 bg-background p-4">
<View className="bg-cardBackground rounded-lg p-3 mb-4">
<Text className="text-base text-fontPrimary">Hello</Text>
</View>
</View>
Clean, readable, and no StyleSheet boilerplate.
The Wrong Way: Light and Dark Classes
When you first add dark mode with NativeWind, you might do this:
<Text className="text-light-fontPrimary dark:text-dark-fontPrimary">
Song Title
</Text>
<View className="bg-light-background dark:bg-dark-background">
<Text className="text-light-fontSecondary dark:text-dark-fontSecondary">
Artist Name
</Text>
</View>
This works, but it's painful. Every color needs two classes. Your JSX becomes cluttered. Changing a color means updating it in multiple places.
There's a better way.
The Right Way: CSS Variables
Use CSS variables for theme colors. Define colors once, use them everywhere, and they automatically switch with the theme.
Step 1: Define Your Theme Colors
Create a theme file with CSS variables using NativeWind's vars helper:
// theme/index.ts
import { vars } from 'nativewind';
export const nativeWindThemes = {
light: vars({
'--color-fontPrimary': '#212121',
'--color-fontSecondary': '#616161',
'--color-background': '#F5F5F5',
'--color-cardBackground': '#FFFFFF',
'--color-button': '#4CAF50',
'--color-border': '#E0E0E0',
}),
dark: vars({
'--color-fontPrimary': '#FFFFFF',
'--color-fontSecondary': '#B0B0B0',
'--color-background': '#121212',
'--color-cardBackground': '#1C1C1C',
'--color-button': '#00C853',
'--color-border': '#2C2C2C',
}),
};
Step 2: Configure Tailwind
Map your CSS variables to Tailwind classes:
// tailwind.config.js
module.exports = {
darkMode: 'class',
content: ['./App.tsx', './src/**/*.{js,jsx,ts,tsx}'],
presets: [require('nativewind/preset')],
theme: {
extend: {
colors: {
// CSS variable-based colors
fontPrimary: 'var(--color-fontPrimary)',
fontSecondary: 'var(--color-fontSecondary)',
background: 'var(--color-background)',
cardBackground: 'var(--color-cardBackground)',
button: 'var(--color-button)',
border: 'var(--color-border)',
// Static colors that don't change
white: '#FFFFFF',
black: '#000000',
red: '#FF0045',
},
},
},
};
Step 3: Apply CSS Variables at Root
Wrap your app with a root View that applies the CSS variables:
// ThemeProvider.tsx
import { useAtomValue } from 'jotai';
import { View } from 'react-native';
import { nativeWindThemes } from './theme';
import { isDarkModeAtom } from './store/theme';
export const ThemeProvider = ({ children }) => {
const isDark = useAtomValue(isDarkModeAtom);
return (
<View
style={nativeWindThemes[isDark ? 'dark' : 'light']}
className={`flex-1 ${isDark ? 'dark' : ''}`}
>
{children}
</View>
);
};
That's it. Now your CSS variables are applied to the entire app.
Using It: Before and After
Before (the wrong way):
<View className="bg-light-cardBackground dark:bg-dark-cardBackground p-4 rounded-lg border border-light-border dark:border-dark-border">
<Text className="text-lg text-light-fontPrimary dark:text-dark-fontPrimary mb-2">
Now Playing
</Text>
<Text className="text-sm text-light-fontSecondary dark:text-dark-fontSecondary">
Artist Name
</Text>
</View>
After (the right way):
<View className="bg-cardBackground p-4 rounded-lg border border-border">
<Text className="text-lg text-fontPrimary mb-2">
Now Playing
</Text>
<Text className="text-sm text-fontSecondary">
Artist Name
</Text>
</View>
Look at that. Clean, simple, readable. Colors automatically switch based on theme. No conditional classes. This approach also works perfectly with SVG icons that adapt to your theme.
Here's a complete example of a music player card with theming:
function MusicPlayerCard({ song }) {
return (
<View className="bg-cardBackground p-4 rounded-lg border border-border mb-4">
<View className="flex-row items-center mb-3">
<View className="w-12 h-12 bg-button rounded-full items-center justify-center">
<Text className="text-white text-lg">▶</Text>
</View>
<View className="ml-3 flex-1">
<Text className="text-base text-fontPrimary font-semibold">
{song.title}
</Text>
<Text className="text-sm text-fontSecondary">
{song.artist}
</Text>
</View>
</View>
<View className="h-1 bg-background rounded-full overflow-hidden">
<View
className="h-full bg-button"
style={{ width: `${song.progress}%` }}
/>
</View>
</View>
);
}
This component works in both light and dark mode automatically. Change the theme, and all colors update instantly.
Managing Theme State
I use Jotai for state management—it works beautifully with React Native and keeps things simple:
// store/theme.ts
import { atom } from 'jotai';
import { Appearance } from 'react-native';
// Track system theme
export const systemThemeAtom = atom<'light' | 'dark'>(
Appearance.getColorScheme() === 'dark' ? 'dark' : 'light'
);
// User's theme preference ('system' | 'light' | 'dark')
export const themeModeAtom = atom<'system' | 'light' | 'dark'>('system');
// Derived atom: actual theme to use
export const isDarkModeAtom = atom((get) => {
const themeMode = get(themeModeAtom);
const systemTheme = get(systemThemeAtom);
if (themeMode === 'system') {
return systemTheme === 'dark';
}
return themeMode === 'dark';
});
Listen for system theme changes:
useEffect(() => {
const subscription = Appearance.addChangeListener(({ colorScheme }) => {
setSystemTheme(colorScheme === 'dark' ? 'dark' : 'light');
});
return () => subscription?.remove();
}, []);
Why This Approach Wins
Simple classes - No more light: and dark: prefixes everywhere
Automatic theming - Colors switch instantly when theme changes
Single source of truth - All colors defined in one place
Easy maintenance - Change a color once, updates everywhere
Type-safe - Define colors in TypeScript for autocomplete and safety
Consistent - Same pattern across your entire app
Static Colors
Some colors don't change with theme:
colors: {
// Theme colors (CSS variables)
fontPrimary: 'var(--color-fontPrimary)',
background: 'var(--color-background)',
// Static colors
white: '#FFFFFF',
black: '#000000',
red: '#FF0045',
}
Use static colors for things like error messages, success states, or brand colors that should stay consistent.
The Learning Curve
I'll be honest—the first few days with Tailwind felt weird. I kept reaching for StyleSheet. Writing long className strings felt wrong.
But after forcing myself to use it for a week, something clicked. I started thinking in utility classes. I stopped context-switching between JSX and style objects. I was just... faster.
Now when I see StyleSheet code, it feels clunky. Creating style objects, naming them, scrolling to find definitions—it all feels like unnecessary friction.
Tips for Getting Started
Start small - Convert one screen at a time. Don't rewrite your entire app.
Use the docs - NativeWind's documentation is excellent. Keep it open while you work.
Learn the shortcuts - p-4 = padding 16, mb-2 = margin-bottom 8. You'll memorize these quickly.
Don't fight it - If you're writing custom styles a lot, you're doing it wrong. Find the utility class.
Use CSS variables for themes - Don't use light: and dark: classes. Use the CSS variable approach.
Know the limits - Unfortunately, not every library supports className. Some third-party components only accept the style prop. When that happens, you'll need to fall back to inline styles or StyleSheet. It doesn't happen so often, but it happens. For responsive sizing, you might still want a simple responsive helper for certain use cases.
Installation
Quick setup guide:
npm install nativewind
npm install --save-dev tailwindcss@3
Important: React Native currently only supports Tailwind CSS 3.X. Make sure to install version 3, not version 4.
Initialize Tailwind:
npx tailwindcss init
Add to tailwind.config.js:
module.exports = {
content: ['./App.tsx', './src/**/*.{js,jsx,ts,tsx}'],
presets: [require('nativewind/preset')],
};
Import in your entry file:
import './global.css';
That's it. Start using className.
Final Thoughts
NativeWind isn't perfect. Sometimes you need inline styles for dynamic values. Sometimes the class names get long. But for 95% of your styling needs, it's the best solution for React Native.
The CSS variable approach for theming is what makes it really shine. Clean code, automatic theme switching, and easy maintenance. Combine this with React Hooks for logic, React Query for data fetching, and i18next for localization, and you have a modern, productive React Native stack.
If you're still using StyleSheet, give NativeWind a try. Push through the first few days of discomfort. I promise—once you get used to it, you won't go back.
Frequently Asked Questions
Does NativeWind affect performance?
No. NativeWind compiles Tailwind classes to React Native styles at build time. The runtime performance is identical to using StyleSheet directly—no performance penalty.
Can I use Tailwind classes directly from web examples?
Mostly yes! Common utilities like flex, text-center, bg-blue-500 work identically. Some web-specific classes (like hover:, focus:) don't apply to mobile. Check NativeWind docs for mobile-specific utilities.
How do I handle custom design tokens?
Extend your tailwind.config.js with custom colors, spacing, fonts, etc. NativeWind respects all Tailwind configuration, so your design system integrates seamlessly.
Can I use NativeWind with third-party libraries?
Most libraries work fine. Some only accept style prop instead of className—in those cases, fall back to inline styles or StyleSheet for that specific component.
How do I implement dark mode with NativeWind?
Use CSS variables in your global.css with .dark class variants. NativeWind automatically applies dark mode styles when you toggle the theme. See the dark mode section above for details.