Skip to content

Fix: NativeWind Not Working — Styles Not Applying, Dark Mode Broken, or Metro Bundler Errors

FixDevs ·

Quick Answer

How to fix NativeWind issues — Tailwind CSS for React Native setup, Metro bundler configuration, className prop, dark mode, responsive styles, and Expo integration.

The Problem

Tailwind classes have no effect on React Native components:

import { View, Text } from 'react-native';

function App() {
  return (
    <View className="flex-1 items-center justify-center bg-blue-500">
      <Text className="text-white text-2xl font-bold">Hello</Text>
    </View>
  );
}
// No styles applied — plain unstyled view

Or Metro bundler throws an error:

error: SyntaxError: Unexpected token (NativeWind CSS)

Or dark mode classes don’t work:

<View className="bg-white dark:bg-gray-900">
// Always shows white — dark mode is ignored

Why This Happens

NativeWind bridges Tailwind CSS and React Native. It compiles Tailwind utilities into React Native StyleSheet objects at build time:

  • NativeWind v4 requires a Metro CSS transformer — the Metro bundler must be configured to process CSS. Without the transformer, className props are ignored because React Native doesn’t natively support CSS classes.
  • Tailwind must be configured with the right content pathstailwind.config.js needs to scan your React Native component files. If the content array doesn’t include your source files, no utilities are generated.
  • className must be enabled on React Native components — in NativeWind v4, components from react-native automatically support className. But custom components or third-party libraries need wrapping with cssInterop or remapProps.
  • Dark mode requires useColorScheme — React Native doesn’t have CSS media queries. NativeWind uses React Native’s useColorScheme hook. The dark: variant only works when the system color scheme is dark or you manually set it.

Fix 1: Setup NativeWind v4 with Expo

npx expo install nativewind tailwindcss react-native-reanimated
npx expo install -- --save-dev prettier-plugin-tailwindcss
// tailwind.config.js
/** @type {import('tailwindcss').Config} */
module.exports = {
  content: [
    './app/**/*.{js,jsx,ts,tsx}',
    './components/**/*.{js,jsx,ts,tsx}',
    './src/**/*.{js,jsx,ts,tsx}',
  ],
  presets: [require('nativewind/preset')],
  theme: {
    extend: {
      colors: {
        primary: '#3b82f6',
        secondary: '#64748b',
      },
    },
  },
  plugins: [],
};
/* global.css */
@tailwind base;
@tailwind components;
@tailwind utilities;
// 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' });
// app/_layout.tsx (Expo Router) or App.tsx
import '../global.css';  // Import CSS at the entry point

export default function RootLayout() {
  return (
    <Stack>
      <Stack.Screen name="index" />
    </Stack>
  );
}
// nativewind-env.d.ts — TypeScript support
/// <reference types="nativewind/types" />

Fix 2: Use Tailwind Classes

import { View, Text, Pressable, ScrollView, Image } from 'react-native';

function HomeScreen() {
  return (
    <ScrollView className="flex-1 bg-white dark:bg-gray-900">
      {/* Layout */}
      <View className="px-4 py-6">
        <Text className="text-3xl font-bold text-gray-900 dark:text-white">
          Dashboard
        </Text>
        <Text className="text-gray-500 dark:text-gray-400 mt-1">
          Welcome back!
        </Text>
      </View>

      {/* Card */}
      <View className="mx-4 p-4 bg-white dark:bg-gray-800 rounded-2xl shadow-md border border-gray-100 dark:border-gray-700">
        <View className="flex-row items-center gap-3">
          <Image
            source={{ uri: 'https://example.com/avatar.jpg' }}
            className="w-12 h-12 rounded-full"
          />
          <View className="flex-1">
            <Text className="text-lg font-semibold text-gray-900 dark:text-white">
              Alice Johnson
            </Text>
            <Text className="text-sm text-gray-500">[email protected]</Text>
          </View>
        </View>
      </View>

      {/* Button */}
      <Pressable className="mx-4 mt-4 bg-primary py-3 rounded-xl active:opacity-80">
        <Text className="text-white text-center font-semibold text-lg">
          Continue
        </Text>
      </Pressable>

      {/* Responsive — platform-specific */}
      <View className="mt-4 px-4 ios:pb-8 android:pb-4">
        <Text className="text-sm text-gray-400">
          Platform-specific padding
        </Text>
      </View>
    </ScrollView>
  );
}

Fix 3: Dark Mode

import { useColorScheme } from 'nativewind';
import { View, Text, Pressable } from 'react-native';

function SettingsScreen() {
  const { colorScheme, setColorScheme, toggleColorScheme } = useColorScheme();

  return (
    <View className="flex-1 bg-white dark:bg-gray-900 p-4">
      <Text className="text-xl font-bold text-gray-900 dark:text-white">
        Appearance
      </Text>

      <View className="mt-4 gap-2">
        <Pressable
          onPress={() => setColorScheme('light')}
          className={`p-4 rounded-xl border ${
            colorScheme === 'light'
              ? 'border-primary bg-blue-50 dark:bg-blue-900/20'
              : 'border-gray-200 dark:border-gray-700'
          }`}
        >
          <Text className="font-medium text-gray-900 dark:text-white">Light</Text>
        </Pressable>

        <Pressable
          onPress={() => setColorScheme('dark')}
          className={`p-4 rounded-xl border ${
            colorScheme === 'dark'
              ? 'border-primary bg-blue-50 dark:bg-blue-900/20'
              : 'border-gray-200 dark:border-gray-700'
          }`}
        >
          <Text className="font-medium text-gray-900 dark:text-white">Dark</Text>
        </Pressable>

        <Pressable
          onPress={() => setColorScheme('system')}
          className="p-4 rounded-xl border border-gray-200 dark:border-gray-700"
        >
          <Text className="font-medium text-gray-900 dark:text-white">System</Text>
        </Pressable>
      </View>
    </View>
  );
}

Fix 4: Third-Party Component Styling

// Third-party components don't support className by default
// Use cssInterop to add className support

import { cssInterop } from 'nativewind';
import { SafeAreaView } from 'react-native-safe-area-context';
import Svg, { Path } from 'react-native-svg';
import { BlurView } from 'expo-blur';

// Remap className to the component's style prop
cssInterop(SafeAreaView, { className: 'style' });
cssInterop(BlurView, { className: 'style' });
cssInterop(Svg, { className: { target: 'style', nativeStyleToProp: { width: true, height: true } } });

// Now you can use className on these components
<SafeAreaView className="flex-1 bg-white">
  <BlurView className="absolute inset-0" intensity={80} />
</SafeAreaView>

Fix 5: Custom Components

// Reusable styled components
import { View, Text, Pressable, type PressableProps } from 'react-native';
import { cva, type VariantProps } from 'class-variance-authority';
import { cn } from '@/lib/utils';

// cn utility
export function cn(...inputs: (string | undefined | false)[]) {
  return inputs.filter(Boolean).join(' ');
}

// Button with variants
const buttonVariants = cva(
  'flex-row items-center justify-center rounded-xl',
  {
    variants: {
      variant: {
        default: 'bg-primary active:bg-blue-600',
        secondary: 'bg-gray-100 dark:bg-gray-800 active:bg-gray-200',
        destructive: 'bg-red-500 active:bg-red-600',
        outline: 'border-2 border-gray-300 dark:border-gray-600 active:bg-gray-50',
        ghost: 'active:bg-gray-100 dark:active:bg-gray-800',
      },
      size: {
        sm: 'px-3 py-2',
        md: 'px-4 py-3',
        lg: 'px-6 py-4',
      },
    },
    defaultVariants: {
      variant: 'default',
      size: 'md',
    },
  }
);

const buttonTextVariants = cva('font-semibold text-center', {
  variants: {
    variant: {
      default: 'text-white',
      secondary: 'text-gray-900 dark:text-white',
      destructive: 'text-white',
      outline: 'text-gray-900 dark:text-white',
      ghost: 'text-gray-900 dark:text-white',
    },
    size: {
      sm: 'text-sm',
      md: 'text-base',
      lg: 'text-lg',
    },
  },
  defaultVariants: { variant: 'default', size: 'md' },
});

interface ButtonProps extends PressableProps, VariantProps<typeof buttonVariants> {
  title: string;
  className?: string;
}

export function Button({ title, variant, size, className, ...props }: ButtonProps) {
  return (
    <Pressable className={cn(buttonVariants({ variant, size }), className)} {...props}>
      <Text className={buttonTextVariants({ variant, size })}>{title}</Text>
    </Pressable>
  );
}

// Usage
<Button title="Sign In" variant="default" size="lg" />
<Button title="Cancel" variant="outline" />
<Button title="Delete" variant="destructive" />

Fix 6: Animations and Transitions

// NativeWind v4 supports transition utilities with Reanimated
import { View, Pressable, Text } from 'react-native';

function AnimatedCard() {
  const [expanded, setExpanded] = useState(false);

  return (
    <Pressable onPress={() => setExpanded(!expanded)}>
      <View
        className={cn(
          'bg-white dark:bg-gray-800 rounded-2xl p-4 transition-all duration-300',
          expanded ? 'shadow-xl scale-[1.02]' : 'shadow-md',
        )}
      >
        <Text className="font-bold text-lg text-gray-900 dark:text-white">
          Tap to expand
        </Text>
        {expanded && (
          <Text className="mt-2 text-gray-600 dark:text-gray-400">
            Additional content shown when expanded.
          </Text>
        )}
      </View>
    </Pressable>
  );
}

Still Not Working?

Classes have no effect — the Metro config is missing withNativeWind. Add it to metro.config.js and restart Metro: npx expo start --clear. Also import global.css in your root layout.

“Unexpected token” in Metro bundler — Metro can’t process CSS without the NativeWind transformer. Ensure withNativeWind(config, { input: './global.css' }) wraps your Metro config. Clear the cache: npx expo start --clear.

Dark mode doesn’t workdark: classes require the system to be in dark mode, or use setColorScheme('dark') from useColorScheme(). NativeWind respects the system setting by default. To override, wrap your app in a color scheme provider.

Third-party components ignore className — only React Native core components support className out of the box. Use cssInterop() to add support to third-party components like SafeAreaView, BlurView, etc.

For related mobile issues, see Fix: Expo Not Working and Fix: React Native Reanimated Not Working.

F

FixDevs

Solo developer based in Japan. Every solution is cross-referenced with official documentation and tested before publishing.

Was this article helpful?

Related Articles