Claude
Skills
Sign in
Back

migrate-nativewind-to-uniwind

Included with Lifetime
$97 forever

Migrate a React Native project from NativeWind to Uniwind. Use when the user wants to replace NativeWind with Uniwind, upgrade from NativeWind, switch to Uniwind, or mentions NativeWind-to-Uniwind migration. Handles package removal, config migration, Tailwind 4 upgrade, cssInterop removal, theme conversion, and all breaking changes.

Ads & Marketing

What this skill does


# Migrate NativeWind to Uniwind

Uniwind replaces NativeWind with better performance and stability. It requires **Tailwind CSS 4** and uses CSS-based theming instead of JS config.

## Pre-Migration Checklist

Before starting, read the project's existing config files to understand the current setup:
- `package.json` (NativeWind version, dependencies)
- `tailwind.config.js` / `tailwind.config.ts`
- `metro.config.js`
- `babel.config.js`
- `global.css` or equivalent CSS entry file
- `nativewind-env.d.ts` or `nativewind.d.ts`
- Any file using `cssInterop` or `remapProps` from `nativewind`
- Any file importing from `react-native-css-interop`
- Any ThemeProvider from NativeWind (`vars()` usage)

## Step 1: Remove NativeWind and Related Packages

Uninstall ALL of these packages (if present):
```bash
npm uninstall nativewind react-native-css-interop
# or
yarn remove nativewind react-native-css-interop
# or
bun remove nativewind react-native-css-interop
```

**CRITICAL**: `react-native-css-interop` is a NativeWind dependency that must be removed. It is commonly missed during migration. Search the entire codebase for any imports from it:
```bash
rg "react-native-css-interop" -g "*.{ts,tsx,js,jsx}"
```

Remove every import and usage found.

## Step 2: Install Uniwind and Tailwind 4

```bash
npm install uniwind tailwindcss@latest
# or
yarn add uniwind tailwindcss@latest
# or
bun add uniwind tailwindcss@latest
```

Ensure `tailwindcss` is version 4+.

## Step 3: Update babel.config.js

Remove the NativeWind babel preset:
```js
// REMOVE this line from presets array:
// 'nativewind/babel'
```

No Uniwind babel preset is needed.

## Step 4: Update metro.config.js

Replace NativeWind's metro config with Uniwind's. `withUniwindConfig` must be the **outermost wrapper**.

**Before (NativeWind):**
```js
const { withNativeWind } = require('nativewind/metro');
module.exports = withNativeWind(config, { input: './global.css' });
```

**After (Uniwind):**
```js
const { getDefaultConfig } = require('expo/metro-config');
// For bare RN: const { getDefaultConfig } = require('@react-native/metro-config');
const { withUniwindConfig } = require('uniwind/metro');

const config = getDefaultConfig(__dirname);

module.exports = withUniwindConfig(config, {
  cssEntryFile: './global.css',
  polyfills: { rem: 14 },
});
```

`cssEntryFile` must be a **relative path string** from project root (e.g. `./global.css` or `./app/global.css`).
Do **not** use absolute paths or `path.resolve(...)` / `path.join(...)` for this option.

```js
// ❌ Broken
cssEntryFile: path.resolve(__dirname, 'app', 'global.css')

// ✅ Correct
cssEntryFile: './app/global.css'
```

**Always set `polyfills.rem` to 14** to match NativeWind's default rem value and prevent spacing/sizing differences after migration.

If the project uses custom themes beyond `light`/`dark` (e.g. defined via NativeWind's `vars()` or a custom ThemeProvider), register them with `extraThemes`. Do NOT include `light` or `dark` — they are added automatically:

```js
module.exports = withUniwindConfig(config, {
  cssEntryFile: './global.css',
  polyfills: { rem: 14 },
  extraThemes: ['ocean', 'sunset', 'premium'],
});
```

Options:
- `cssEntryFile` (required): relative path string to CSS entry file (from project root)
- `polyfills.rem` (required for migration): set to `14` to match NativeWind's rem base
- `extraThemes` (required if project has custom themes): array of custom theme names — do NOT include `light`/`dark`
- `dtsFile` (optional): path for generated TypeScript types, defaults to `./uniwind-types.d.ts`
- `debug` (optional): log unsupported CSS properties during dev

## Step 5: Update global.css

Replace NativeWind's Tailwind 3 directives with Tailwind 4 imports:

**Before:**
```css
@tailwind base;
@tailwind components;
@tailwind utilities;
```

**After:**
```css
@import 'tailwindcss';
@import 'uniwind';
```

## Step 6: Update CSS Entry Import

Ensure `global.css` is imported in your main App component (e.g., `App.tsx`), NOT in the root `index.ts`/`index.js` where you register the app — importing there breaks hot reload.

## Step 7: Delete NativeWind Type Definitions

Delete `nativewind-env.d.ts` or `nativewind.d.ts`. Uniwind auto-generates its own types at the path specified by `dtsFile`.

## Step 8: Delete tailwind.config.js

Remove `tailwind.config.js` / `tailwind.config.ts` entirely. All theme config moves to CSS using Tailwind 4's `@theme` directive.

Migrate custom theme values to `global.css`:

**Before (tailwind.config.js):**
```js
module.exports = {
  theme: {
    extend: {
      colors: {
        primary: '#00a8ff',
        secondary: '#273c75',
      },
      fontFamily: {
        normal: ['Roboto-Regular'],
        bold: ['Roboto-Bold'],
      },
    },
  },
};
```

**After (global.css):**
```css
@import 'tailwindcss';
@import 'uniwind';

@theme {
  --color-primary: #00a8ff;
  --color-secondary: #273c75;
  --font-normal: 'Roboto-Regular';
  --font-bold: 'Roboto-Bold';
}
```

Font families must specify a **single font** — React Native doesn't support font fallbacks.

## Step 9: Remove ALL cssInterop and remapProps Usage

**This is the most commonly missed step.** Search the entire codebase:

```bash
rg "cssInterop|remapProps" -g "*.{ts,tsx,js,jsx}"
```

Replace every `cssInterop()` / `remapProps()` call with Uniwind's `withUniwind()`:

**Before (NativeWind):**
```tsx
import { cssInterop } from 'react-native-css-interop';
import { Image } from 'expo-image';

cssInterop(Image, { className: 'style' });
```

**After (Uniwind):**
```tsx
import { withUniwind } from 'uniwind';
import { Image as ExpoImage } from 'expo-image';

export const Image = withUniwind(ExpoImage);
```

`withUniwind` automatically maps `className` → `style` and other common props. For custom prop mappings:

```tsx
const StyledProgressBar = withUniwind(ProgressBar, {
  width: {
    fromClassName: 'widthClassName',
    styleProperty: 'width',
  },
});
```

Define wrapped components at **module level** (not inside render functions). Each component should only be wrapped once:

- **Used in one file only** — define the wrapped component in that same file:
  ```tsx
  // screens/ProfileScreen.tsx
  import { withUniwind } from 'uniwind';
  import { BlurView as RNBlurView } from '@react-native-community/blur';

  const BlurView = withUniwind(RNBlurView);

  export function ProfileScreen() {
    return <BlurView className="flex-1" />;
  }
  ```

- **Used across multiple files** — wrap once in a shared module and re-export:
  ```tsx
  // components/styled.ts
  import { withUniwind } from 'uniwind';
  import { Image as ExpoImage } from 'expo-image';
  import { LinearGradient as RNLinearGradient } from 'expo-linear-gradient';

  export const Image = withUniwind(ExpoImage);
  export const LinearGradient = withUniwind(RNLinearGradient);
  ```
  Then import from the shared module everywhere:
  ```tsx
  import { Image, LinearGradient } from '@/components/styled';
  ```

Never call `withUniwind` on the same component in multiple files — wrap once, import everywhere.

**IMPORTANT**: Do NOT wrap components from `react-native` or `react-native-reanimated` with `withUniwind` — they already support `className` out of the box. This includes `View`, `Text`, `Image`, `ScrollView`, `FlatList`, `Pressable`, `TextInput`, `Animated.View`, etc. Only use `withUniwind` for **third-party** components (e.g. `expo-image`, `expo-linear-gradient`, `@react-native-community/blur`).

**IMPORTANT — accent- prefix for non-style color props**: React Native components have props like `color`, `tintColor`, `backgroundColor` that are NOT part of the `style` object. To set these via Tailwind classes, use the `accent-` prefix with the corresponding `*ClassName` prop:

```tsx
// color prop → colorClassName with accent- prefix
<ActivityIndicator
    className="m-4"
    size="large"
    colorClassName="accent-blue-500 dark:accent-blue-400"
/>

// color prop on Button
<Button
    colorClassName="accent-background"

Related in Ads & Marketing