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):
npm uninstall nativewind react-native-css-interop
yarn remove nativewind react-native-css-interop
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:
rg "react-native-css-interop" -g "*.{ts,tsx,js,jsx}"
Remove every import and usage found.
Step 2: Install Uniwind and Tailwind 4
npm install uniwind tailwindcss@latest
yarn add uniwind tailwindcss@latest
bun add uniwind tailwindcss@latest
Ensure tailwindcss is version 4+.
Step 3: Update babel.config.js
Remove the NativeWind babel preset:
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):
const { withNativeWind } = require('nativewind/metro');
module.exports = withNativeWind(config, { input: './global.css' });
After (Uniwind):
const { getDefaultConfig } = require('expo/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.
cssEntryFile: path.resolve(__dirname, 'app', 'global.css')
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:
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:
@tailwind base;
@tailwind components;
@tailwind utilities;
After:
@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):
module.exports = {
theme: {
extend: {
colors: {
primary: '#00a8ff',
secondary: '#273c75',
},
fontFamily: {
normal: ['Roboto-Regular'],
bold: ['Roboto-Bold'],
},
},
},
};
After (global.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:
rg "cssInterop|remapProps" -g "*.{ts,tsx,js,jsx}"
Replace every cssInterop() / remapProps() call with Uniwind's withUniwind():
Before (NativeWind):
import { cssInterop } from 'react-native-css-interop';
import { Image } from 'expo-image';
cssInterop(Image, { className: 'style' });
After (Uniwind):
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:
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:
import { withUniwind } from 'uniwind';
import { BlurView as RNBlurView } from '@react-native-community/blur';
const BlurView = withUniwind(RNBlurView);
export function ProfileScreen() <