A mobile phone displaying Tailwind utility classes in a React Native app on a modern desk

Uniwind: Tailwind Finally Feels at Home in React Native

The Gray Cat
The Gray Cat

If you have ever wished you could just slap a className="flex-1 bg-white p-4" onto a React Native View and have it work, Uniwind is here to grant that wish. Created by the team behind Unistyles, one of the most respected styling libraries in the React Native ecosystem, Uniwind delivers Tailwind CSS utility classes directly to your native components with styles computed at build time rather than runtime. The result is a developer experience that feels like writing Tailwind for the web, but with the performance characteristics mobile apps demand.

What Makes Uniwind Stand Out

Uniwind is not the first attempt at bringing Tailwind to React Native, but it takes a fundamentally different approach. Instead of interpreting class names at runtime, Uniwind processes them during the build step through a Metro transformer or Vite plugin. This means your styles are already resolved by the time your app launches, eliminating the overhead that runtime solutions introduce.

Beyond raw performance, Uniwind ships with a thoughtful feature set: dark mode with automatic system theme detection, scoped themes for applying different visual treatments to component subtrees, platform-specific variants like ios: and android:, pseudo-class support for interactive states, and safe area utilities that understand device notches and home indicators. It requires Tailwind CSS 4, embracing the latest version rather than maintaining backward compatibility with older configurations.

Getting Started

Installation is straightforward. You need both Uniwind and Tailwind CSS 4:

// Using npm
npm install uniwind tailwindcss

// Using yarn
yarn add uniwind tailwindcss

If you prefer starting from a template, Uniwind offers ready-made setups:

npx create-expo-app -e with-router-uniwind

Wiring Up Your Bundler

After installing, create a global.css file that imports both Tailwind and Uniwind:

@import 'tailwindcss';
@import 'uniwind';

Then configure your Metro bundler to use the Uniwind transformer:

const { withUniwindConfig } = require('uniwind/metro');

const config = getDefaultConfig(__dirname);

module.exports = withUniwindConfig(config, {
  cssEntryFile: './src/global.css',
  dtsFile: './src/uniwind-types.d.ts',
});

The dtsFile option generates TypeScript definitions so your editor can autocomplete class names. If you are using Vite for React Native Web, the setup is even simpler:

import { uniwind } from 'uniwind/vite';

export default defineConfig({
  plugins: [uniwind()],
});

Your First Styled Components

Laying Out a Screen

Every React Native component gets a className prop automatically. No wrappers, no higher-order components, no special imports beyond your usual React Native ones:

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

export function ProfileCard() {
  return (
    <View className="flex-1 bg-white dark:bg-gray-900 p-4">
      <Image
        className="w-24 h-24 rounded-full self-center"
        source={{ uri: 'https://example.com/avatar.png' }}
      />
      <Text className="text-xl font-bold text-gray-900 dark:text-white text-center mt-4">
        Jane Developer
      </Text>
      <Text className="text-sm text-gray-500 dark:text-gray-400 text-center">
        React Native Engineer
      </Text>
    </View>
  );
}

Notice the dark: prefixes. Uniwind detects the system color scheme automatically when you use the built-in system theme, and those variants activate without any additional configuration.

Handling Interactive States

Uniwind supports pseudo-class variants for interactive components. The focus:, active:, and disabled: prefixes work on Pressable and other touchable elements:

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

export function ActionButton({ label, onPress, disabled }: {
  label: string;
  onPress: () => void;
  disabled?: boolean;
}) {
  return (
    <Pressable
      className="bg-blue-600 active:bg-blue-700 disabled:bg-gray-300 rounded-lg py-3 px-6"
      onPress={onPress}
      disabled={disabled}
    >
      <Text className="text-white font-semibold text-center disabled:text-gray-500">
        {label}
      </Text>
    </Pressable>
  );
}

Platform-Specific Styling

One of Uniwind's unique features is platform variants. You can target specific platforms directly in your class names:

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

export function PlatformHeader() {
  return (
    <View className="p-4 ios:pt-14 android:pt-8 bg-white dark:bg-gray-900">
      <Text className="text-lg font-bold ios:font-medium android:font-bold">
        My App
      </Text>
    </View>
  );
}

The ios:, android:, web:, tv:, android-tv:, and apple-tv: prefixes let you fine-tune styles per platform without conditional logic in your component code.

Going Deeper

Theme Orchestration

Uniwind ships with three built-in themes: light, dark, and system. The system theme automatically follows the device color scheme, while setting light or dark explicitly locks the app to that mode and updates native system dialogs to match.

You can switch themes at runtime through the global Uniwind object:

import { Uniwind } from 'uniwind';

function toggleTheme() {
  const current = Uniwind.currentTheme;
  if (current === 'light') {
    Uniwind.setTheme('dark');
  } else {
    Uniwind.setTheme('light');
  }
}

For custom themes, define CSS variables in your Tailwind configuration and switch between them. The useUniwind hook lets you reactively access the current theme state:

import { useUniwind } from 'uniwind';

function ThemeIndicator() {
  const { theme, hasAdaptiveThemes } = useUniwind();

  return (
    <Text className="text-sm text-gray-600 dark:text-gray-400">
      Current theme: {theme} {hasAdaptiveThemes ? '(auto)' : '(manual)'}
    </Text>
  );
}

Scoped Themes for Component Subtrees

Since version 1.4.0, Uniwind supports scoped themes. This lets you apply a different theme to a specific portion of your component tree without affecting the rest of the app. Think of a settings panel that previews what dark mode looks like while the rest of the app stays in light mode, or a card component that always renders in a branded color scheme regardless of the global theme.

Wrapping Third-Party Components

Not every component in your app will be from React Native core. The withUniwind higher-order component adds className support to any third-party component that accepts a style prop:

import { withUniwind } from 'uniwind';
import { LinearGradient } from 'expo-linear-gradient';

const StyledGradient = withUniwind(LinearGradient);

function GradientCard() {
  return (
    <StyledGradient className="flex-1 rounded-2xl p-6">
      <Text className="text-white text-lg font-bold">
        Styled with Uniwind
      </Text>
    </StyledGradient>
  );
}

Safe Area Awareness

Mobile devices have notches, dynamic islands, and home indicators that eat into your layout. Uniwind provides safe area utilities that handle this elegantly:

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

export function SafeScreen() {
  return (
    <View className="flex-1 bg-white p-safe">
      <Text className="text-lg">
        This content respects device safe areas on all sides.
      </Text>
    </View>
  );
}

The p-safe, m-safe, and inset-safe utilities automatically account for device-specific insets without importing SafeAreaView or managing inset values manually.

Free vs Pro: Choosing Your Path

Uniwind follows a freemium model. The free version covers the vast majority of use cases and is what most projects will need. The Pro version, built on the proven Unistyles C++ engine, adds zero-rerender updates through Shadow Tree manipulation, Reanimated 4 animation support via class names, automatic font scaling, and native-layer safe area insets. If your app has demanding performance requirements or complex animation needs, the Pro version is worth evaluating. For most projects, the free version delivers an excellent experience.

A Few Things to Keep in Mind

React Native uses the Yoga layout engine, which means flexbox is the default layout model with a column direction. Some web-specific Tailwind utilities are not available: hover: and visited: pseudo-classes, CSS Grid, float-based layouts, and pseudo-elements like before: and after:. These are fundamental platform differences rather than Uniwind limitations. The library supports the full range of utilities that make sense in a native context, including layout, spacing, typography, colors, borders, transforms, and positioning.

Wrapping Up

Uniwind brings a mature, performance-first approach to Tailwind styling in React Native. The build-time computation strategy, combined with thoughtful features like scoped themes, platform variants, and safe area utilities, makes it a compelling choice for teams that want the productivity of utility-first CSS in their mobile apps. Backed by the Unistyles team's deep experience with React Native internals, it has quickly gained traction with over 1,300 GitHub stars and 76,000 weekly downloads in its first eight months. Whether you are starting a new React Native project or migrating from another styling solution, Uniwind deserves a serious look.