migrate-nativewind-to-uniwind

安装量: 602
排名: #1868

安装

npx skills add https://github.com/uni-stack/uniwind --skill migrate-nativewind-to-uniwind

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

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: 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

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: // 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): const { withNativeWind } = require ( 'nativewind/metro' ) ; module . exports = withNativeWind ( config , { input : './global.css' } ) ; After (Uniwind): 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. // ❌ 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: 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:
// 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:
// 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:
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: // 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 } /> Rule: className accepts any Tailwind utility for style-based props. For non-style props (color, tintColor, etc.), use {propName}ClassName with the accent- prefix. This applies to all built-in React Native components. Step 10: Migrate NativeWind Theme Variables Before (NativeWind JS themes with 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 ] }

After (Uniwind CSS themes): @layer theme { :root { @variant light { --color-primary :

00a8ff

; --color-typography :

000

; } @variant dark { --color-primary :

273c75

; --color-typography :

fff

;
}
}
}
IMPORTANT
All theme variants must define the exact same set of CSS variables. If light defines --color-primary and --color-typography , then dark (and any custom theme) must also define both. Mismatched variables will cause a Uniwind runtime error. No ThemeProvider wrapper needed. Remove the NativeWind or vars() wrapper from JSX. Keep React Navigation's if used. If the project used nested theme wrappers to preview or force a theme for a specific subtree (for example a demo card, settings preview, or side-by-side theme comparison), use Uniwind Pro's ScopedTheme instead of changing the global theme: import { ScopedTheme } from 'uniwind' ; < ScopedTheme theme = " dark "

< PreviewCard /> </ ScopedTheme

If the project has custom themes beyond light/dark (e.g. ocean , premium ), you must: Define them in CSS using @variant : @layer theme { :root { @variant ocean { --color-primary :

0ea5e9

; --color-background :

0c4a6e

; } } } Register them in metro.config.js via extraThemes (skip light / dark — they are auto-added): module . exports = withUniwindConfig ( config , { cssEntryFile : './global.css' , polyfills : { rem : 14 } , extraThemes : [ 'ocean' , 'premium' ] , } ) ; Step 11: Migrate Safe Area Utilities NativeWind's safe area classes need explicit setup in Uniwind: 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

) ; } Step 12: Verify rem Value NativeWind uses 14px as the base rem, Uniwind defaults to 16px. Step 4 already sets polyfills: { rem: 14 } in metro config to preserve NativeWind's spacing. If the user explicitly wants Uniwind's default (16px), they can remove the polyfill — but warn them that all spacing/sizing will shift. Step 13: Handle className Deduplication Uniwind does NOT auto-deduplicate conflicting classNames (NativeWind did). If your codebase relies on override patterns like className={p-4 ${overrideClass}} , set up a cn utility. First, check if the project already has a cn helper (common in shadcn/ui projects): rg "export function cn|export const cn" -g ".{ts,tsx,js}" If it exists, keep it as-is. If not, install dependencies and create it: npm install tailwind-merge clsx Create lib/cn.ts (or wherever utils live in the project): import { type ClassValue , clsx } from 'clsx' ; import { twMerge } from 'tailwind-merge' ; export function cn ( ... inputs : ClassValue [ ] ) { return twMerge ( clsx ( inputs ) ) ; } Usage: 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' ) } /> Use cn instead of raw twMerge — it handles conditional classes, arrays, and falsy values via clsx before deduplicating with tailwind-merge . Step 14: Update Animated Class Names If the project used NativeWind animated- / transition class patterns, migrate those to explicit react-native-reanimated usage. Uniwind OSS does not provide NativeWind-style animated class behavior. Use this migration guide section as the source of truth: https://docs.uniwind.dev/migration-from-nativewind Step 15: Clean Up Remaining NativeWind References Final sweep — search for and remove any remaining references: rg "nativewind|NativeWind|native-wind" -g "*.{ts,tsx,js,jsx,json,css}" Check for: NativeWind imports in any file nativewind in package.json (devDependencies too) react-native-css-interop in package.json NativeWind babel preset in babel.config.js NativeWind metro wrapper in metro.config.js nativewind-env.d.ts or nativewind.d.ts files Any cssInterop() or remapProps() calls Any vars() imports from nativewind Uniwind APIs & Patterns useUniwind — Theme Access (re-renders on change) Docs: https://docs.uniwind.dev/api/use-uniwind import { useUniwind } from 'uniwind' ; const { theme , hasAdaptiveThemes } = useUniwind ( ) ; // theme: current theme name — "light", "dark", "system", or custom // hasAdaptiveThemes: true if app follows system color scheme Use for: displaying theme name in UI, conditional rendering by theme, side effects on theme change. Uniwind Static API — Theme Access (no re-render) Access theme info without causing re-renders: import { Uniwind } from 'uniwind' ; Uniwind . currentTheme // "light", "dark", "system", or custom Uniwind . hasAdaptiveThemes // true if following system color scheme Use for: logging, analytics, imperative logic outside render. useResolveClassNames — Convert classNames to Style Objects Docs: https://docs.uniwind.dev/api/use-resolve-class-names Converts Tailwind classes into React Native style objects. Use when working with components that don't support className and can't be wrapped with withUniwind (e.g. react-navigation theme config): import { 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 , } } /> useCSSVariable — Access CSS Variables in JS Docs: https://docs.uniwind.dev/api/use-css-variable Retrieve CSS variable values programmatically. Variable must be prefixed with -- and match a variable defined in global.css : import { useCSSVariable } from 'uniwind' ; const primaryColor = useCSSVariable ( '--color-primary' ) ; const spacing = useCSSVariable ( '--spacing-4' ) ; Use for: animations, third-party library configs, calculations with design tokens. CSS Functions — Custom Utilities Docs: https://docs.uniwind.dev/api/css-functions Define custom utilities using device-aware CSS functions like hairlineWidth() , fontScale() , pixelRatio() . These can be used everywhere (custom CSS classes, @utility , etc.) — but NOT inside @theme {} (which only accepts static values). Use @utility to create reusable Tailwind-style classes: @utility w-hairline { width : hairlineWidth ( ) ; } @utility h-hairline { height : hairlineWidth ( ) ; } @utility border-hairline { border-width : hairlineWidth ( ) ; } @utility text-scaled { font-size : fontScale ( ) ; } Then use as: Platform Selectors Docs: https://docs.uniwind.dev/api/platform-select Apply styles conditionally per platform using ios: , android: , web: , native: prefixes: < 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

Theme Switching Docs: https://docs.uniwind.dev/theming/basics By default Uniwind follows the system color scheme (adaptive themes). To switch themes programmatically: 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) ScopedTheme — Theme a Subtree Only Docs: https://docs.uniwind.dev/api/scoped-themes Use ScopedTheme when the project needs a different theme for only part of the UI (component previews, themed sections, nested demos) without changing the app-wide theme: import { ScopedTheme } from 'uniwind' ; < View className = " gap-3 "

< PreviewCard /> < ScopedTheme theme = " light "

< PreviewCard /> </ ScopedTheme

< ScopedTheme theme = " dark "

< PreviewCard /> </ ScopedTheme

</ View

Important behavior: Nearest ScopedTheme wins (nested scopes are supported) Hooks like useUniwind , useResolveClassNames , and useCSSVariable resolve against the nearest scoped theme withUniwind -wrapped third-party components inside the scope also resolve themed values from that scope Custom theme names can be used in ScopedTheme (must be defined in extraThemes ) Style Based on Themes — Prefer CSS Variables Docs: https://docs.uniwind.dev/theming/style-based-on-themes Prefer using CSS variable-based classes over explicit dark:/light: variants. Instead of: // Avoid this pattern < View className = " light:bg-white dark:bg-black " /> Define a CSS variable and use it directly: @layer theme { :root { @variant light { --color-background :

ffffff

; } @variant dark { --color-background :

000000

;
}
}
}
// Preferred — automatically adapts to theme
<
View
className
=
"
bg-background
"
/>
This is cleaner, easier to maintain, and works automatically with custom themes too.
Runtime CSS Variable Updates
Docs:
https://docs.uniwind.dev/theming/update-css-variables
Update theme variables at runtime, e.g. based on user preferences or API responses:
import
{
Uniwind
}
from
'uniwind'
;
// Preconfigure theme based on user input or API response
Uniwind
.
updateCSSVariables
(
'light'
,
{
'--color-primary'
:
'#ff6600'
,
'--color-background'
:
'#1a1a2e'
,
}
)
;
This pattern should be used only when the app has real runtime theming needs (for example, user-selected brand colors or API-driven themes).
Variants with tailwind-variants
Docs:
https://docs.uniwind.dev/tailwind-basics#advanced-pattern-variants-and-compound-variants
For component variants and compound variants, use the
tailwind-variants
library:
import
{
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'
}
)
}
/>
Monorepo Support
Docs:
https://docs.uniwind.dev/monorepos
If the project is a monorepo, add
@source
directives in
global.css
so Tailwind scans packages outside the CSS entry file's directory (only if that directory has components with Tailwind classes):
@import
'tailwindcss'
;
@import
'uniwind'
;
@source
"../../packages/ui/src"
;
@source
"../../packages/shared/src"
;
FAQ
Docs:
https://docs.uniwind.dev/faq
Custom Fonts
Uniwind maps className to font-family only — font files must be loaded separately (expo-font plugin in
app.json
or
react-native-asset
for bare RN). Font family names in
@theme
must exactly match filenames (without extension). Use
@variant
for per-platform fonts (must be inside
@layer theme { :root { } }
):
@layer
theme
{
:root
{
@variant
ios
{
--font-sans
:
'SF Pro Text'
;
}
@variant
android
{
--font-sans
:
'Roboto-Regular'
;
}
@variant
web
{
--font-sans
:
'system-ui'
;
}
}
}
Data Selectors
Use
data-[prop=value]:utility
for prop-based styling. Only equality checks supported:
<
View
data-state
=
{
isOpen
?
'open'
:
'closed'
}
className
=
"
data-[state=open]:bg-muted/50
"
/>
global.css Location in Expo Router
Place at project root and import in root layout (
app/_layout.tsx
). If placed in
app/
, components outside need
@source
directives. Tailwind scans from
global.css
location.
Full App Reloads on CSS Changes
Metro can't hot-reload files with many providers. Move
global.css
import deeper in the component tree (e.g. navigation root or home screen) to fix.
Gradients
Built-in support, no extra deps needed. Use
bg-gradient-to-r from-red-500 via-yellow-500 to-green-500
. For
expo-linear-gradient
, use
useCSSVariable
to get colors —
withUniwind
won't work since gradient props are arrays.
Style Specificity
Inline
style
always overrides
className
. Use
className
for static styles, inline only for truly dynamic values. Avoid mixing both for the same property.
Serialization Errors
(
Failed to serialize javascript object
): Clear caches:
watchman watch-del-all 2>/dev/null; rm -rf node_modules/.cache && npx expo start --clear
. Common causes: complex
@theme
configs, circular CSS variable references.
Metro unstable_enablePackageExports Conflicts
Some apps (crypto etc.) disable this, breaking Uniwind. Use selective resolver:
config
.
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
)
;
}
;
Safe Area Classes
:
p-safe
,
pt-safe
,
pb-safe
,
px-safe
,
py-safe
,
m-safe
,
mt-safe
, etc. Also supports
-or-{value}
(min spacing) and
-offset-{value}
(extra spacing) variants.
Next.js
Not officially supported. Uniwind is for Metro and Vite. Community plugin:
uniwind-plugin-next
. For Next.js, use standard Tailwind CSS and share design tokens.
Vite
Supported since v1.2.0. Use
uniwind/vite
plugin alongside
@tailwindcss/vite
.
UI Kits
HeroUI Native, react-native-reusables and Gluestack 4.1+ works great with Uniwind
Known Issues & Gotchas
data-
attributes
*: Uniwind supports
data-[prop=value]:utility
syntax for conditional styling, similar to NativeWind.
Animated styles
Migrate NativeWind animated classes to
react-native-reanimated
directly. Uniwind Pro has built-in Reanimated support.
Verification
After migration, verify:
npx react-native start --reset-cache
(clear Metro cache) or with expo
npx expo start -c
All screens render correctly on iOS and Android
Theme switching works (light/dark)
Custom fonts load correctly
Safe area insets apply properly
No console warnings about missing styles
No remaining imports from
nativewind
or
react-native-css-interop
IMPORTANT
Do NOT guess Uniwind APIs. If you are unsure about any Uniwind API, hook, component, or configuration option, fetch and verify against the official docs: https://docs.uniwind.dev/llms-full.txt
返回排行榜