Skip to main content

NativeWind vs Tamagui vs twrnc: React Native Styling in 2026

·PkgPulse Team

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 variablestext-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-native and next.js without 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: and lg: 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

FeatureNativeWindTamaguitwrnc
Tailwind syntax✅ FullPartial ($theme tokens)✅ Full
Web + NativeLimited (Native focus)✅ Universal❌ Native only
Build-time compilation✅ Metro transformer✅ Babel plugin❌ Runtime
Dark modedark: classes✅ Theme tokensManual conditional
Responsive breakpointsmd:, lg:$gtSm, $gtMd
CSS variablesVia tokens
Component library❌ Styling only✅ 80+ components❌ Styling only
TypeScript
Expo support✅ Official
Config complexityMediumHighLow
Runtime overheadMinimal (compiled)Minimal (compiled)Medium (runtime parsing)
Learning curveLow (know Tailwind?)Medium-HighVery Low
GitHub stars~9k~12k~4k
Web CSS outputVia 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.

Comments

Stay Updated

Get the latest package insights, npm trends, and tooling tips delivered to your inbox.