Loading...
Loading...
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.
npx skill4agent add uni-stack/uniwind migrate-nativewind-to-uniwindpackage.jsontailwind.config.jstailwind.config.tsmetro.config.jsbabel.config.jsglobal.cssnativewind-env.d.tsnativewind.d.tscssInteropremapPropsnativewindreact-native-css-interopvars()npm uninstall nativewind react-native-css-interop
# or
yarn remove nativewind react-native-css-interop
# or
bun remove nativewind react-native-css-interopreact-native-css-interoprg "react-native-css-interop" -g "*.{ts,tsx,js,jsx}"npm install uniwind tailwindcss@latest
# or
yarn add uniwind tailwindcss@latest
# or
bun add uniwind tailwindcss@latesttailwindcss// REMOVE this line from presets array:
// 'nativewind/babel'withUniwindConfigconst { withNativeWind } = require('nativewind/metro');
module.exports = withNativeWind(config, { input: './global.css' });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 },
});polyfills.remlightdarkvars()extraThemeslightdarkmodule.exports = withUniwindConfig(config, {
cssEntryFile: './global.css',
polyfills: { rem: 14 },
extraThemes: ['ocean', 'sunset', 'premium'],
});cssEntryFilepolyfills.rem14extraThemeslightdarkdtsFile./uniwind-types.d.tsdebug@tailwind base;
@tailwind components;
@tailwind utilities;@import 'tailwindcss';
@import 'uniwind';global.cssApp.tsxindex.tsindex.jsnativewind-env.d.tsnativewind.d.tsdtsFiletailwind.config.jstailwind.config.ts@themeglobal.cssmodule.exports = {
theme: {
extend: {
colors: {
primary: '#00a8ff',
secondary: '#273c75',
},
fontFamily: {
normal: ['Roboto-Regular'],
bold: ['Roboto-Bold'],
},
},
},
};@import 'tailwindcss';
@import 'uniwind';
@theme {
--color-primary: #00a8ff;
--color-secondary: #273c75;
--font-normal: 'Roboto-Regular';
--font-bold: 'Roboto-Bold';
}rg "cssInterop|remapProps" -g "*.{ts,tsx,js,jsx}"cssInterop()remapProps()withUniwind()import { cssInterop } from 'react-native-css-interop';
import { Image } from 'expo-image';
cssInterop(Image, { className: 'style' });import { withUniwind } from 'uniwind';
import { Image as ExpoImage } from 'expo-image';
export const Image = withUniwind(ExpoImage);withUniwindclassNamestyleconst StyledProgressBar = withUniwind(ProgressBar, {
width: {
fromClassName: 'widthClassName',
styleProperty: 'width',
},
});// 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" />;
}// 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);import { Image, LinearGradient } from '@/components/styled';withUniwindreact-nativereact-native-reanimatedwithUniwindclassNameViewTextImageScrollViewFlatListPressableTextInputAnimated.ViewwithUniwindexpo-imageexpo-linear-gradient@react-native-community/blurcolortintColorbackgroundColorstyleaccent-*ClassName// 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"
title="Press me"
/>
// tintColor prop → tintColorClassName with accent- prefix
<Image
className="w-6 h-6"
tintColorClassName="accent-red-500"
source={icon}
/>className{propName}ClassNameaccent-vars()import { vars } from 'nativewind';
export const themes = {
light: vars({
'--color-primary': '#00a8ff',
'--color-typography': '#000',
}),
dark: vars({
'--color-primary': '#273c75',
'--color-typography': '#fff',
}),
};
// In JSX:
<View style={themes[colorScheme]}>@layer theme {
:root {
@variant light {
--color-primary: #00a8ff;
--color-typography: #000;
}
@variant dark {
--color-primary: #273c75;
--color-typography: #fff;
}
}
}light--color-primary--color-typographydark<ThemeProvider>vars()<ThemeProvider>oceanpremium@variant@layer theme {
:root {
@variant ocean {
--color-primary: #0ea5e9;
--color-background: #0c4a6e;
}
}
}extraThemeslightdarkmodule.exports = withUniwindConfig(config, {
cssEntryFile: './global.css',
polyfills: { rem: 14 },
extraThemes: ['ocean', 'premium'],
});import { SafeAreaProvider, SafeAreaListener } from 'react-native-safe-area-context';
import { Uniwind } from 'uniwind';
export default function App() {
return (
<SafeAreaProvider>
<SafeAreaListener
onChange={({ insets }) => {
Uniwind.updateInsets(insets);
}}
>
<View className="pt-safe px-safe">
{/* content */}
</View>
</SafeAreaListener>
</SafeAreaProvider>
);
}polyfills: { rem: 14 }className={`p-4 ${overrideClass}`}cncnrg "export function cn|export const cn" -g "*.{ts,tsx,js}"npm install tailwind-merge clsxlib/cn.tsimport { type ClassValue, clsx } from 'clsx';
import { twMerge } from 'tailwind-merge';
export function cn(...inputs: ClassValue[]) {
return twMerge(clsx(inputs));
}import { cn } from '@/lib/cn';
<View className={cn('p-4 bg-white', props.className)} />
<Text className={cn('text-base', isActive && 'text-blue-500', disabled && 'opacity-50')} />cntwMergeclsxtailwind-mergeanimated-*react-native-reanimatedrg "nativewind|NativeWind|native-wind" -g "*.{ts,tsx,js,jsx,json,css}"nativewindpackage.jsonreact-native-css-interoppackage.jsonbabel.config.jsmetro.config.jsnativewind-env.d.tsnativewind.d.tscssInterop()remapProps()vars()nativewindimport { useUniwind } from 'uniwind';
const { theme, hasAdaptiveThemes } = useUniwind();
// theme: current theme name — "light", "dark", "system", or custom
// hasAdaptiveThemes: true if app follows system color schemeimport { Uniwind } from 'uniwind';
Uniwind.currentTheme // "light", "dark", "system", or custom
Uniwind.hasAdaptiveThemes // true if following system color schemeclassNamewithUniwindimport { useResolveClassNames } from 'uniwind';
const headerStyle = useResolveClassNames('bg-blue-500');
const cardStyle = useResolveClassNames('bg-white dark:bg-gray-900');
<Stack.Navigator
screenOptions={{
headerStyle: headerStyle,
cardStyle: cardStyle,
}}
/>--global.cssimport { useCSSVariable } from 'uniwind';
const primaryColor = useCSSVariable('--color-primary');
const spacing = useCSSVariable('--spacing-4');hairlineWidth()fontScale()pixelRatio()@theme {
--width-hairline: hairlineWidth();
}<View className="w-hairline" />ios:android:web:native:<View className="ios:bg-red-500 android:bg-blue-500 web:bg-green-500">
<Text className="ios:text-white android:text-white web:text-black">
Platform-specific styles
</Text>
</View>import { Uniwind } from 'uniwind';
Uniwind.setTheme('dark'); // force dark
Uniwind.setTheme('light'); // force light
Uniwind.setTheme('system'); // follow system (default)
Uniwind.setTheme('ocean'); // custom theme (must be in extraThemes)// Avoid this pattern
<View className="light:bg-white dark:bg-black" />@layer theme {
:root {
@variant light { --color-background: #ffffff; }
@variant dark { --color-background: #000000; }
}
}// Preferred — automatically adapts to theme
<View className="bg-background" />import { Uniwind } from 'uniwind';
// Preconfigure theme based on user input or API response
Uniwind.updateCSSVariables('light', {
'--color-primary': '#ff6600',
'--color-background': '#1a1a2e',
});tailwind-variantsimport { tv } from 'tailwind-variants';
const button = tv({
base: 'px-4 py-2 rounded-lg',
variants: {
color: {
primary: 'bg-primary text-white',
secondary: 'bg-secondary text-white',
},
size: {
sm: 'text-sm',
lg: 'text-lg px-6 py-3',
},
},
});
<Pressable className={button({ color: 'primary', size: 'lg' })} />@sourceglobal.css@import 'tailwindcss';
@import 'uniwind';
@source "../../packages/ui/src";
@source "../../packages/shared/src";app.jsonreact-native-asset@theme@media ios { --font-sans: 'SF Pro Text'; }
@media android { --font-sans: 'Roboto-Regular'; }data-[prop=value]:utility<View data-state={isOpen ? 'open' : 'closed'} className="data-[state=open]:bg-muted/50" />app/_layout.tsxapp/@sourceglobal.cssglobal.cssbg-gradient-to-r from-red-500 via-yellow-500 to-green-500expo-linear-gradientuseCSSVariablewithUniwindstyleclassNameclassNameFailed to serialize javascript objectwatchman watch-del-all 2>/dev/null; rm -rf node_modules/.cache && npx expo start --clear@themeconfig.resolver.unstable_enablePackageExports = false;
config.resolver.resolveRequest = (context, moduleName, platform) => {
if (['uniwind', 'culori'].some((prefix) => moduleName.startsWith(prefix))) {
return context.resolveRequest({ ...context, unstable_enablePackageExports: true }, moduleName, platform);
}
return context.resolveRequest(context, moduleName, platform);
};p-safept-safepb-safepx-safepy-safem-safemt-safe-or-{value}-offset-{value}uniwind-plugin-nextuniwind/vite@tailwindcss/vitedata-[prop=value]:utilityreact-native-reanimatednpx react-native start --reset-cachenpx expo start -cnativewindreact-native-css-interop