--- name: migrate-nativewind-to-uniwind description: > 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. --- # 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 }, }); ``` **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): path to CSS entry file - `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 ; } ``` - **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 // color prop on Button