NativeWind vs Tamagui vs twrnc: React Native Styling in 2026
NativeWind vs Tamagui vs twrnc: React Native Styling in 2026
TL;DR
React Native's StyleSheet API is verbose and lacks the utility-first ergonomics web developers expect. NativeWind brings Tailwind CSS syntax to React Native — you write className="flex-1 bg-blue-500 p-4" and it works. Tamagui is the most powerful option — a universal UI system for both React Native and web with its own compiler that generates platform-optimized styles at build time. twrnc (Tailwind React Native Classnames) is the lightest option — pure runtime Tailwind class parsing with zero config, perfect for smaller projects. For Tailwind-native developers migrating to React Native: NativeWind. For universal web + native apps with performance demands: Tamagui. For a quick Tailwind drop-in with minimal setup: twrnc.
Key Takeaways
- NativeWind v4 uses a CSS-to-StyleSheet compiler — Tailwind classes are processed at build time, not runtime, for near-native performance
- Tamagui GitHub stars: ~12k — rapidly growing, used by Vercel, and major React Native apps
- twrnc is the simplest setup — one import, no config, works with any Tailwind class in seconds
- Tamagui's compiler is unique — extracts static styles at build time, eliminating JS thread overhead
- NativeWind v4 supports CSS variables —
text-primary, custom design tokens work just like web Tailwind - All three work with Expo — NativeWind and twrnc are easier; Tamagui requires more config
- Tamagui is the only universal solution — same component works on
react-nativeandnext.jswithout conditional logic
The React Native Styling Problem
React Native's built-in StyleSheet requires JavaScript objects:
// Verbose StyleSheet API — verbose and un-shareable with web
const styles = StyleSheet.create({
container: {
flex: 1,
backgroundColor: '#3b82f6',
paddingHorizontal: 16,
paddingVertical: 12,
alignItems: 'center',
},
text: {
color: '#ffffff',
fontSize: 16,
fontWeight: '600',
},
});
function MyComponent() {
return (
<View style={styles.container}>
<Text style={styles.text}>Hello</Text>
</View>
);
}
The problems:
- No Tailwind-style utilities — every style is defined separately
- No responsive breakpoints —
md:andlg:classes don't exist - Web + native codebases can't share styles
- Dark mode requires manual conditional logic
- No design tokens — colors are arbitrary hex values, not
blue-500
NativeWind: Tailwind CSS for React Native
NativeWind v4 (the current stable version as of 2026) processes Tailwind classes at build time using a Metro transformer. The output is compiled StyleSheet objects — performance-equivalent to native.
Installation (Expo)
npx expo install nativewind tailwindcss react-native-reanimated react-native-safe-area-context
// tailwind.config.js
/** @type {import('tailwindcss').Config} */
module.exports = {
content: ["./app/**/*.{js,jsx,ts,tsx}", "./components/**/*.{js,jsx,ts,tsx}"],
presets: [require("nativewind/preset")],
theme: {
extend: {},
},
plugins: [],
};
// babel.config.js
module.exports = function (api) {
api.cache(true);
return {
presets: [
["babel-preset-expo", { jsxImportSource: "nativewind" }],
"nativewind/babel",
],
};
};
// metro.config.js
const { getDefaultConfig } = require("expo/metro-config");
const { withNativeWind } = require("nativewind/metro");
const config = getDefaultConfig(__dirname);
module.exports = withNativeWind(config, { input: "./global.css" });
/* global.css */
@tailwind base;
@tailwind components;
@tailwind utilities;
Basic Usage
import { View, Text, Pressable } from "react-native";
// className works just like web Tailwind
export function Card({ title, description }: { title: string; description: string }) {
return (
<View className="bg-white dark:bg-gray-900 rounded-2xl p-6 shadow-lg mx-4">
<Text className="text-gray-900 dark:text-white font-bold text-xl mb-2">
{title}
</Text>
<Text className="text-gray-500 dark:text-gray-400 text-sm leading-relaxed">
{description}
</Text>
</View>
);
}
// Interactive states work natively
export function PrimaryButton({ label, onPress }: { label: string; onPress: () => void }) {
return (
<Pressable
onPress={onPress}
className="bg-blue-600 active:bg-blue-700 rounded-xl px-6 py-3 items-center"
>
<Text className="text-white font-semibold text-base">{label}</Text>
</Pressable>
);
}
CSS Variables and Design Tokens
/* global.css — define tokens */
@layer base {
:root {
--color-primary: 59 130 246;
--color-surface: 255 255 255;
}
.dark {
--color-primary: 96 165 250;
--color-surface: 17 24 39;
}
}
// tailwind.config.js
module.exports = {
theme: {
extend: {
colors: {
primary: "rgb(var(--color-primary) / <alpha-value>)",
surface: "rgb(var(--color-surface) / <alpha-value>)",
},
},
},
};
// Use token-based classes anywhere
<View className="bg-surface border-primary/20">
<Text className="text-primary font-semibold">Token-based design</Text>
</View>
Responsive Classes
// NativeWind supports breakpoints on native too
// md: triggers when screen width >= 768px (tablets)
<View className="flex-col md:flex-row gap-4">
<View className="flex-1 bg-blue-100 p-4 rounded-xl">
<Text className="text-sm md:text-base">Responsive layout</Text>
</View>
</View>
Dark Mode
// Automatic dark mode via colorScheme — no conditional logic
<View className="bg-white dark:bg-gray-900">
<Text className="text-gray-900 dark:text-white">Automatic dark mode</Text>
</View>
// Or manual control
import { useColorScheme } from "nativewind";
function ThemeToggle() {
const { colorScheme, setColorScheme } = useColorScheme();
return (
<Pressable
onPress={() => setColorScheme(colorScheme === "dark" ? "light" : "dark")}
className="bg-gray-100 dark:bg-gray-800 p-3 rounded-full"
>
<Text>{colorScheme === "dark" ? "☀️" : "🌙"}</Text>
</Pressable>
);
}
Tamagui: Universal UI System
Tamagui is a design system + styling solution that works on React Native AND web (next.js, vite). Its compiler analyzes your components at build time and extracts static styles, eliminating runtime overhead. It ships its own component library on top.
Installation
npx expo install tamagui @tamagui/config @tamagui/expo-plugin
npx expo install @tamagui/babel-plugin
// tamagui.config.ts
import { createTamagui } from "@tamagui/core";
import { config } from "@tamagui/config/v3";
const tamaguiConfig = createTamagui(config);
export default tamaguiConfig;
export type Conf = typeof tamaguiConfig;
declare module "@tamagui/core" {
interface TamaguiCustomConfig extends Conf {}
}
// babel.config.js
module.exports = {
presets: ["babel-preset-expo"],
plugins: [
[
"@tamagui/babel-plugin",
{
components: ["tamagui"],
config: "./tamagui.config.ts",
logTimings: true,
disableExtraction: process.env.NODE_ENV === "development",
},
],
],
};
// app/_layout.tsx
import { TamaguiProvider } from "tamagui";
import config from "../tamagui.config";
export default function RootLayout({ children }: { children: React.ReactNode }) {
return <TamaguiProvider config={config}>{children}</TamaguiProvider>;
}
Core Styling API
import { View, Text, Stack, XStack, YStack, Button } from "tamagui";
// Stack = View, XStack = horizontal View, YStack = vertical View
export function ProductCard({ name, price }: { name: string; price: string }) {
return (
<YStack
backgroundColor="$background"
borderRadius="$4"
padding="$4"
gap="$2"
shadowColor="$shadowColor"
shadowRadius={8}
>
<Text fontWeight="700" fontSize="$6" color="$color">
{name}
</Text>
<XStack alignItems="center" justifyContent="space-between">
<Text color="$colorSubtle" fontSize="$3">
Starting at
</Text>
<Text fontWeight="600" fontSize="$5" color="$blue10">
{price}
</Text>
</XStack>
<Button
size="$4"
theme="blue"
borderRadius="$3"
onPress={() => {}}
>
Add to Cart
</Button>
</YStack>
);
}
Custom Themes
// tamagui.config.ts — custom tokens and themes
import { createTokens, createTheme } from "@tamagui/core";
const tokens = createTokens({
size: { 0: 0, 1: 4, 2: 8, 3: 12, 4: 16, true: 16, 5: 20, 6: 24, 7: 32, 8: 40 },
space: { 0: 0, 1: 4, 2: 8, 3: 12, 4: 16, true: 16, 5: 20, 6: 24, 7: 32 },
radius: { 0: 0, 1: 4, 2: 8, 3: 12, 4: 16, 5: 24, true: 8 },
color: {
brandPrimary: "#6366f1",
brandSecondary: "#8b5cf6",
},
});
const lightTheme = createTheme({
background: "#ffffff",
color: "#1a1a1a",
colorSubtle: "#6b7280",
brandPrimary: tokens.color.brandPrimary,
});
const darkTheme = createTheme({
background: "#0f0f0f",
color: "#f5f5f5",
colorSubtle: "#9ca3af",
brandPrimary: tokens.color.brandSecondary,
});
Tamagui Compiler in Action
// This component:
function Hero() {
return (
<YStack flex={1} backgroundColor="$background" padding={20}>
<Text color="$color" fontSize={24}>Welcome</Text>
</YStack>
);
}
// Tamagui compiler extracts to React Native StyleSheet at build time:
// StyleSheet.create({
// _style1: { flex: 1, backgroundColor: theme.background, padding: 20 },
// _style2: { color: theme.color, fontSize: 24 },
// })
// Zero JS thread overhead at runtime — equivalent to native StyleSheet.create
Universal Web + Native
// This exact component works on React Native AND Next.js
import { Button, Text, YStack } from "tamagui";
export function SignupCTA() {
return (
<YStack
padding="$6"
alignItems="center"
gap="$4"
$gtSm={{ flexDirection: "row" }} // Desktop: side by side
>
<Text fontSize="$7" fontWeight="700">
Start for free
</Text>
<Button size="$5" theme="active" onPress={() => {}}>
Get Started
</Button>
</YStack>
);
}
twrnc: Tailwind React Native Classnames
twrnc is the minimal approach — a runtime Tailwind class parser for React Native. No compiler, no build step, no config. Just install and use Tailwind class strings.
Installation
npm install twrnc
Basic Usage
import tw from "twrnc";
import { View, Text, Pressable } from "react-native";
// tw`class-string` returns a StyleSheet-compatible object
export function Badge({ label, variant = "default" }: { label: string; variant?: "default" | "success" | "error" }) {
const variantStyles = {
default: tw`bg-gray-100 text-gray-700`,
success: tw`bg-green-100 text-green-700`,
error: tw`bg-red-100 text-red-700`,
};
return (
<View style={tw`px-3 py-1 rounded-full`}>
<Text style={[tw`text-xs font-medium`, variantStyles[variant]]}>
{label}
</Text>
</View>
);
}
Dynamic Styles
import tw from "twrnc";
// Template literals for dynamic class composition
function ProgressBar({ progress }: { progress: number }) {
const width = Math.min(100, Math.max(0, progress));
return (
<View style={tw`bg-gray-200 h-2 rounded-full overflow-hidden`}>
<View
style={[
tw`bg-blue-500 h-full rounded-full`,
{ width: `${width}%` },
]}
/>
</View>
);
}
// Conditional classes
function StatusChip({ active }: { active: boolean }) {
return (
<View style={tw`px-3 py-1 rounded-full ${active ? "bg-green-500" : "bg-gray-300"}`}>
<Text style={tw`text-white text-sm font-medium`}>
{active ? "Active" : "Inactive"}
</Text>
</View>
);
}
Custom Configuration
// tw-config.js — extend twrnc with custom values
module.exports = {
theme: {
extend: {
colors: {
brand: {
50: "#eff6ff",
500: "#3b82f6",
900: "#1e3a8a",
},
},
borderRadius: {
"4xl": "2rem",
},
},
},
};
import { create } from "twrnc";
import customConfig from "./tw-config";
const tw = create(customConfig);
// Now use custom values
<View style={tw`bg-brand-500 rounded-4xl p-4`}>
<Text style={tw`text-brand-50`}>Custom brand colors</Text>
</View>
Dark Mode with twrnc
import { useColorScheme } from "react-native";
import tw from "twrnc";
function DarkModeCard() {
const colorScheme = useColorScheme();
const isDark = colorScheme === "dark";
return (
<View
style={tw`${isDark ? "bg-gray-900" : "bg-white"} p-6 rounded-2xl`}
>
<Text style={tw`${isDark ? "text-white" : "text-gray-900"} font-bold text-lg`}>
Dark mode card
</Text>
</View>
);
}
Feature Comparison
| Feature | NativeWind | Tamagui | twrnc |
|---|---|---|---|
| Tailwind syntax | ✅ Full | Partial ($theme tokens) | ✅ Full |
| Web + Native | Limited (Native focus) | ✅ Universal | ❌ Native only |
| Build-time compilation | ✅ Metro transformer | ✅ Babel plugin | ❌ Runtime |
| Dark mode | ✅ dark: classes | ✅ Theme tokens | Manual conditional |
| Responsive breakpoints | ✅ md:, lg: | ✅ $gtSm, $gtMd | ❌ |
| CSS variables | ✅ | Via tokens | ❌ |
| Component library | ❌ Styling only | ✅ 80+ components | ❌ Styling only |
| TypeScript | ✅ | ✅ | ✅ |
| Expo support | ✅ Official | ✅ | ✅ |
| Config complexity | Medium | High | Low |
| Runtime overhead | Minimal (compiled) | Minimal (compiled) | Medium (runtime parsing) |
| Learning curve | Low (know Tailwind?) | Medium-High | Very Low |
| GitHub stars | ~9k | ~12k | ~4k |
| Web CSS output | Via PostCSS | ✅ Native | ❌ |
Performance Reality
Startup impact (Expo Go, iPhone 15 Pro):
StyleSheet.create (baseline): JS thread: 0ms overhead
NativeWind v4 (compiled): JS thread: ~2ms overhead
Tamagui (compiled): JS thread: ~1ms overhead
twrnc (runtime): JS thread: ~8-15ms per render
For 200 components on screen:
NativeWind: negligible
Tamagui: negligible
twrnc: ~50-100ms total parsing on first render
Verdict: twrnc's runtime parsing is acceptable for most apps.
Only becomes a concern with 100+ dynamically-styled components re-rendering.
When to Use Each
Choose NativeWind if:
- You know Tailwind CSS and want zero learning curve on React Native
- Your app is React Native first (not universal web + native)
- You want
dark:,md:,focus:pseudo-classes from the real Tailwind - CSS variables and design tokens from your web project need to be shared
Choose Tamagui if:
- You're building a universal app (Next.js web + React Native) with shared components
- Performance is critical — Tamagui's compiler is the most aggressive optimizer
- You want an included component library (Button, Input, Sheet, Dialog) not just styling
- Your design system needs a full token system with themes and variants
Choose twrnc if:
- You need Tailwind syntax in React Native with zero configuration
- The project is small-to-medium and runtime parsing overhead is acceptable
- You want to prototype fast without build configuration
- You're adding Tailwind utilities to an existing project with minimal risk
Methodology
Data sourced from GitHub repositories (star counts as of February 2026), official documentation, community benchmarks on React Native performance forums, and the React Native Radio podcast ecosystem discussions. Performance measurements approximate based on community-reported benchmarks across Expo SDK 52 applications. Package download statistics from npm (January 2026).
Related: React Native vs Expo vs Capacitor for choosing your mobile framework, or shadcn/ui vs Radix UI for web component approaches.