migrate-nativewind-to-uniwind

Compare original and translation side by side

🇺🇸

Original

English
🇨🇳

Translation

Chinese

Migrate NativeWind to Uniwind

将NativeWind迁移至Uniwind

Uniwind replaces NativeWind with better performance and stability. It requires Tailwind CSS 4 and uses CSS-based theming instead of JS config.
Uniwind以更优的性能和稳定性替代NativeWind。它要求使用Tailwind CSS 4,并采用基于CSS的主题方案而非JS配置。

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)
开始迁移前,请阅读项目现有配置文件以了解当前设置:
  • package.json
    (NativeWind版本、依赖项)
  • tailwind.config.js
    /
    tailwind.config.ts
  • metro.config.js
  • babel.config.js
  • global.css
    或等效的CSS入口文件
  • nativewind-env.d.ts
    nativewind.d.ts
  • 任何使用
    nativewind
    cssInterop
    remapProps
    的文件
  • 任何从
    react-native-css-interop
    导入的文件
  • 任何来自NativeWind的ThemeProvider(使用
    vars()
    的场景)

Step 1: Remove NativeWind and Related Packages

步骤1:移除NativeWind及相关包

Uninstall ALL of these packages (if present):
bash
npm uninstall nativewind react-native-css-interop
卸载所有以下包(如果存在):
bash
npm uninstall nativewind react-native-css-interop

or

yarn remove nativewind react-native-css-interop
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.
bun remove nativewind react-native-css-interop

**关键注意事项**:`react-native-css-interop`是NativeWind的依赖项,必须移除。迁移过程中经常会遗漏这一步。搜索整个代码库查找任何来自它的导入:
```bash
rg "react-native-css-interop" -g "*.{ts,tsx,js,jsx}"
删除找到的所有导入和用法。

Step 2: Install Uniwind and Tailwind 4

步骤2:安装Uniwind和Tailwind 4

bash
npm install uniwind tailwindcss@latest
bash
npm install uniwind tailwindcss@latest

or

yarn add uniwind tailwindcss@latest
yarn add uniwind tailwindcss@latest

or

bun add uniwind tailwindcss@latest

Ensure `tailwindcss` is version 4+.
bun add uniwind tailwindcss@latest

确保`tailwindcss`版本为4+。

Step 3: Update babel.config.js

步骤3:更新babel.config.js

Remove the NativeWind babel preset:
js
// REMOVE this line from presets array:
// 'nativewind/babel'
No Uniwind babel preset is needed.
移除NativeWind的babel预设:
js
// 从presets数组中移除这一行:
// 'nativewind/babel'
不需要Uniwind的babel预设。

Step 4: Update metro.config.js

步骤4:更新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
用Uniwind的配置替换NativeWind的metro配置。
withUniwindConfig
必须是最外层的包装器
迁移前(NativeWind):
js
const { withNativeWind } = require('nativewind/metro');
module.exports = withNativeWind(config, { input: './global.css' });
迁移后(Uniwind):
js
const { getDefaultConfig } = require('expo/metro-config');
// 纯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.rem
设置为14
,以匹配NativeWind的默认rem值,避免迁移后出现间距/尺寸差异。
如果项目使用
light
/
dark
之外的自定义主题(例如通过NativeWind的
vars()
或自定义ThemeProvider定义),请在
extraThemes
中注册它们。不要包含
light
dark
——它们会自动添加:
js
module.exports = withUniwindConfig(config, {
  cssEntryFile: './global.css',
  polyfills: { rem: 14 },
  extraThemes: ['ocean', 'sunset', 'premium'],
});
选项说明:
  • cssEntryFile
    (必填):CSS入口文件路径
  • polyfills.rem
    (迁移必填):设置为
    14
    以匹配NativeWind的rem基准
  • extraThemes
    (项目有自定义主题时必填):自定义主题名称数组——不要包含
    light
    /
    dark
  • dtsFile
    (可选):生成的TypeScript类型文件路径,默认为
    ./uniwind-types.d.ts
  • debug
    (可选):开发期间记录不支持的CSS属性

Step 5: Update global.css

步骤5:更新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';
用Tailwind 4的导入替换NativeWind的Tailwind 3指令:
迁移前:
css
@tailwind base;
@tailwind components;
@tailwind utilities;
迁移后:
css
@import 'tailwindcss';
@import 'uniwind';

Step 6: Update CSS Entry Import

步骤6:更新CSS入口导入

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.
确保
global.css
在主App组件(如
App.tsx
)中导入,不要在注册应用的根
index.ts
/
index.js
中导入——在那里导入会破坏热重载。

Step 7: Delete NativeWind Type Definitions

步骤7:删除NativeWind类型定义

Delete
nativewind-env.d.ts
or
nativewind.d.ts
. Uniwind auto-generates its own types at the path specified by
dtsFile
.
删除
nativewind-env.d.ts
nativewind.d.ts
。Uniwind会自动在
dtsFile
指定的路径生成自己的类型文件。

Step 8: Delete tailwind.config.js

步骤8:删除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.
完全移除
tailwind.config.js
/
tailwind.config.ts
。所有主题配置将通过Tailwind 4的
@theme
指令移至CSS中。
将自定义主题值迁移到
global.css
迁移前(tailwind.config.js):
js
module.exports = {
  theme: {
    extend: {
      colors: {
        primary: '#00a8ff',
        secondary: '#273c75',
      },
      fontFamily: {
        normal: ['Roboto-Regular'],
        bold: ['Roboto-Bold'],
      },
    },
  },
};
迁移后(global.css):
css
@import 'tailwindcss';
@import 'uniwind';

@theme {
  --color-primary: #00a8ff;
  --color-secondary: #273c75;
  --font-normal: 'Roboto-Regular';
  --font-bold: 'Roboto-Bold';
}
字体系列必须指定单个字体——React Native不支持字体回退。

Step 9: Remove ALL cssInterop and remapProps Usage

步骤9:移除所有cssInterop和remapProps用法

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"
    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.
这是最常被遗漏的步骤。搜索整个代码库:
bash
rg "cssInterop|remapProps" -g "*.{ts,tsx,js,jsx}"
将每个
cssInterop()
/
remapProps()
调用替换为Uniwind的
withUniwind()
迁移前(NativeWind):
tsx
import { cssInterop } from 'react-native-css-interop';
import { Image } from 'expo-image';

cssInterop(Image, { className: 'style' });
迁移后(Uniwind):
tsx
import { withUniwind } from 'uniwind';
import { Image as ExpoImage } from 'expo-image';

export const Image = withUniwind(ExpoImage);
withUniwind
会自动映射
className
style
和其他常见属性。对于自定义属性映射:
tsx
const StyledProgressBar = withUniwind(ProgressBar, {
  width: {
    fromClassName: 'widthClassName',
    styleProperty: 'width',
  },
});
模块级别定义包装后的组件(不要在渲染函数内部)。每个组件只应被包装一次:
  • 仅在一个文件中使用——在同一文件中定义包装后的组件:
    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" />;
    }
  • 在多个文件中使用——在共享模块中包装一次并重新导出:
    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);
    然后在所有需要的地方从共享模块导入:
    tsx
    import { Image, LinearGradient } from '@/components/styled';
不要在多个文件中对同一组件调用
withUniwind
——只包装一次,在各处导入使用。
重要提示:非样式颜色属性的accent-前缀:React Native组件有一些不属于
style
对象的属性,如
color
tintColor
backgroundColor
。要通过Tailwind类设置这些属性,请使用
accent-
前缀和对应的
*ClassName
属性:
tsx
// color属性 → 使用colorClassName和accent-前缀
<ActivityIndicator
    className="m-4"
    size="large"
    colorClassName="accent-blue-500 dark:accent-blue-400"
/>

// Button组件的color属性
<Button
    colorClassName="accent-background"
    title="Press me"
/>

// tintColor属性 → 使用tintColorClassName和accent-前缀
<Image
    className="w-6 h-6"
    tintColorClassName="accent-red-500"
    source={icon}
/>
规则:
className
接受任何用于样式类属性的Tailwind工具类。对于非样式属性(color、tintColor等),使用
{propName}ClassName
并加上
accent-
前缀。这适用于所有内置React Native组件。

Step 10: Migrate NativeWind Theme Variables

步骤10:迁移NativeWind主题变量

Before (NativeWind JS themes with
vars()
):
tsx
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):
css
@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
<ThemeProvider>
or
vars()
wrapper from JSX. Keep React Navigation's
<ThemeProvider>
if used.
If the project has custom themes beyond light/dark (e.g.
ocean
,
premium
), you must:
  1. Define them in CSS using
    @variant
    :
css
@layer theme {
  :root {
    @variant ocean {
      --color-primary: #0ea5e9;
      --color-background: #0c4a6e;
    }
  }
}
  1. Register them in metro.config.js via
    extraThemes
    (skip
    light
    /
    dark
    — they are auto-added):
js
module.exports = withUniwindConfig(config, {
  cssEntryFile: './global.css',
  polyfills: { rem: 14 },
  extraThemes: ['ocean', 'premium'],
});
迁移前(使用
vars()
的NativeWind JS主题):
tsx
import { vars } from 'nativewind';

export const themes = {
  light: vars({
    '--color-primary': '#00a8ff',
    '--color-typography': '#000',
  }),
  dark: vars({
    '--color-primary': '#273c75',
    '--color-typography': '#fff',
  }),
};

// 在JSX中:
<View style={themes[colorScheme]}>
迁移后(Uniwind CSS主题):
css
@layer theme {
  :root {
    @variant light {
      --color-primary: #00a8ff;
      --color-typography: #000;
    }
    @variant dark {
      --color-primary: #273c75;
      --color-typography: #fff;
    }
  }
}
重要提示:所有主题变体必须定义完全相同的CSS变量集。如果
light
定义了
--color-primary
--color-typography
,那么
dark
(以及任何自定义主题)也必须定义这两个变量。变量不匹配会导致Uniwind运行时错误。
不需要ThemeProvider包装器。从JSX中移除NativeWind的
<ThemeProvider>
vars()
包装器。如果使用了React Navigation的
<ThemeProvider>
则保留。
如果项目有light/dark之外的自定义主题(如
ocean
premium
),必须:
  1. 在CSS中使用
    @variant
    定义它们:
css
@layer theme {
  :root {
    @variant ocean {
      --color-primary: #0ea5e9;
      --color-background: #0c4a6e;
    }
  }
}
  1. 在metro.config.js中通过
    extraThemes
    注册它们(跳过
    light
    /
    dark
    ——它们会自动添加):
js
module.exports = withUniwindConfig(config, {
  cssEntryFile: './global.css',
  polyfills: { rem: 14 },
  extraThemes: ['ocean', 'premium'],
});

Step 11: Migrate Safe Area Utilities

步骤11:迁移安全区域工具类

NativeWind's safe area classes need explicit setup in Uniwind:
tsx
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>
  );
}
NativeWind的安全区域类在Uniwind中需要显式设置:
tsx
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">
          {/* 内容 */}
        </View>
      </SafeAreaListener>
    </SafeAreaProvider>
  );
}

Step 12: Verify rem Value

步骤12:验证rem值

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.
NativeWind使用14px作为基准rem,Uniwind默认是16px。步骤4已在metro配置中设置
polyfills: { rem: 14 }
以保留NativeWind的间距。如果用户明确希望使用Uniwind的默认值(16px),可以移除该polyfill,但需提醒他们所有间距/尺寸都会发生变化。

Step 13: Handle className Deduplication

步骤13:处理className重复

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):
bash
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:
bash
npm install tailwind-merge clsx
Create
lib/cn.ts
(or wherever utils live in the project):
ts
import { type ClassValue, clsx } from 'clsx';
import { twMerge } from 'tailwind-merge';

export function cn(...inputs: ClassValue[]) {
    return twMerge(clsx(inputs));
}
Usage:
tsx
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
.
Uniwind不会自动去重冲突的className(NativeWind会)。如果代码库依赖类似
className={`p-4 ${overrideClass}`}
的覆盖模式,请设置
cn
工具。
首先检查项目是否已有
cn
助手(在shadcn/ui项目中很常见):
bash
rg "export function cn|export const cn" -g "*.{ts,tsx,js}"
如果已存在,保持原样。如果不存在,安装依赖并创建:
bash
npm install tailwind-merge clsx
创建
lib/cn.ts
(或项目中工具类所在的位置):
ts
import { type ClassValue, clsx } from 'clsx';
import { twMerge } from 'tailwind-merge';

export function cn(...inputs: ClassValue[]) {
    return twMerge(clsx(inputs));
}
用法:
tsx
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')} />
使用
cn
而非原生
twMerge
——它会通过
clsx
处理条件类、数组和假值,然后用
tailwind-merge
去重。

Step 14: Update Animated Class Names

步骤14:迁移动画类名

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:
如果项目使用了NativeWind的
animated-*
/ 过渡类模式,请将它们迁移为直接使用
react-native-reanimated
。Uniwind开源版本不提供NativeWind风格的动画类行为。
请参考此迁移指南部分作为权威来源:

Step 15: Clean Up Remaining NativeWind References

步骤15:清理剩余的NativeWind引用

Final sweep — search for and remove any remaining references:
bash
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
最终检查——搜索并移除所有剩余引用:
bash
rg "nativewind|NativeWind|native-wind" -g "*.{ts,tsx,js,jsx,json,css}"
检查以下内容:
  • 任何文件中的NativeWind导入
  • package.json
    中的
    nativewind
    (包括devDependencies)
  • package.json
    中的
    react-native-css-interop
  • babel.config.js
    中的NativeWind babel预设
  • metro.config.js
    中的NativeWind metro包装器
  • nativewind-env.d.ts
    nativewind.d.ts
    文件
  • 任何
    cssInterop()
    remapProps()
    调用
  • 任何来自
    nativewind
    vars()
    导入

Uniwind APIs & Patterns

Uniwind API与模式

useUniwind — Theme Access (re-renders on change)

useUniwind — 主题访问(主题变化时重新渲染)

tsx
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.
tsx
import { useUniwind } from 'uniwind';

const { theme, hasAdaptiveThemes } = useUniwind();
// theme: 当前主题名称 — "light", "dark", "system"或自定义主题
// hasAdaptiveThemes: 如果应用跟随系统配色方案则为true
适用场景:在UI中显示主题名称、按主题条件渲染、主题变化时的副作用处理。

Uniwind Static API — Theme Access (no re-render)

Uniwind静态API — 主题访问(无重新渲染)

Access theme info without causing re-renders:
tsx
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.
无需导致重新渲染即可访问主题信息:
tsx
import { Uniwind } from 'uniwind';

Uniwind.currentTheme    // "light", "dark", "system"或自定义主题
Uniwind.hasAdaptiveThemes // 如果跟随系统配色方案则为true
适用场景:日志记录、分析、渲染外的命令式逻辑。

useResolveClassNames — Convert classNames to Style Objects

useResolveClassNames — 将className转换为样式对象

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):
tsx
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,
  }}
/>
将Tailwind类转换为React Native样式对象。适用于不支持
className
且无法用
withUniwind
包装的组件(如react-navigation主题配置):
tsx
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

useCSSVariable — 在JS中访问CSS变量

Retrieve CSS variable values programmatically. Variable must be prefixed with
--
and match a variable defined in
global.css
:
tsx
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变量值。变量必须以
--
为前缀,且与
global.css
中定义的变量匹配:
tsx
import { useCSSVariable } from 'uniwind';

const primaryColor = useCSSVariable('--color-primary');
const spacing = useCSSVariable('--spacing-4');
适用场景:动画、第三方库配置、使用设计令牌的计算。

CSS Functions — Custom Utilities

CSS函数 — 自定义工具类

Define custom utilities using device-aware CSS functions like
hairlineWidth()
,
fontScale()
,
pixelRatio()
:
css
@theme {
  --width-hairline: hairlineWidth();
}
Then use as:
<View className="w-hairline" />
使用感知设备的CSS函数(如
hairlineWidth()
fontScale()
pixelRatio()
)定义自定义工具类:
css
@theme {
  --width-hairline: hairlineWidth();
}
然后这样使用:
<View className="w-hairline" />

Platform Selectors

平台选择器

Apply styles conditionally per platform using
ios:
,
android:
,
web:
,
native:
prefixes:
tsx
<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>
使用
ios:
android:
web:
native:
前缀按平台条件应用样式:
tsx
<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">
    平台专属样式
  </Text>
</View>

Theme Switching

主题切换

By default Uniwind follows the system color scheme (adaptive themes). To switch themes programmatically:
tsx
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)
默认情况下,Uniwind跟随系统配色方案(自适应主题)。要以编程方式切换主题:
tsx
import { Uniwind } from 'uniwind';

Uniwind.setTheme('dark');     // 强制深色模式
Uniwind.setTheme('light');    // 强制浅色模式
Uniwind.setTheme('system');   // 跟随系统(默认)
Uniwind.setTheme('ocean');    // 自定义主题(必须在extraThemes中注册)

Style Based on Themes — Prefer CSS Variables

基于主题的样式 — 优先使用CSS变量

Prefer using CSS variable-based classes over explicit dark:/light: variants. Instead of:
tsx
// Avoid this pattern
<View className="light:bg-white dark:bg-black" />
Define a CSS variable and use it directly:
css
@layer theme {
  :root {
    @variant light { --color-background: #ffffff; }
    @variant dark { --color-background: #000000; }
  }
}
tsx
// Preferred — automatically adapts to theme
<View className="bg-background" />
This is cleaner, easier to maintain, and works automatically with custom themes too.
优先使用基于CSS变量的类而非显式的dark:/light:变体。不要这样写:
tsx
// 避免这种模式
<View className="light:bg-white dark:bg-black" />
定义一个CSS变量并直接使用:
css
@layer theme {
  :root {
    @variant light { --color-background: #ffffff; }
    @variant dark { --color-background: #000000; }
  }
}
tsx
// 推荐方式——自动适配主题
<View className="bg-background" />
这种方式更简洁、更易于维护,并且也能自动适配自定义主题。

Runtime CSS Variable Updates

运行时CSS变量更新

Update theme variables at runtime, e.g. based on user preferences or API responses:
tsx
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).
在运行时更新主题变量,例如基于用户偏好或API响应:
tsx
import { Uniwind } from 'uniwind';

// 根据用户输入或API响应预配置主题
Uniwind.updateCSSVariables('light', {
  '--color-primary': '#ff6600',
  '--color-background': '#1a1a2e',
});
仅当应用有实际的运行时主题需求时才使用此模式(例如用户选择的品牌颜色或API驱动的主题)。

Variants with tailwind-variants

使用tailwind-variants实现变体

For component variants and compound variants, use the
tailwind-variants
library:
tsx
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' })} />
对于组件变体和复合变体,使用
tailwind-variants
库:
tsx
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

monorepo支持

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):
css
@import 'tailwindcss';
@import 'uniwind';
@source "../../packages/ui/src";
@source "../../packages/shared/src";
如果项目是monorepo,在
global.css
中添加
@source
指令,以便Tailwind扫描CSS入口文件目录之外的包(仅当该目录包含带有Tailwind类的组件时):
css
@import 'tailwindcss';
@import 'uniwind';
@source "../../packages/ui/src";
@source "../../packages/shared/src";

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 platform media queries for per-platform fonts:
css
@media ios { --font-sans: 'SF Pro Text'; }
@media android { --font-sans: 'Roboto-Regular'; }
Data Selectors: Use
data-[prop=value]:utility
for prop-based styling. Only equality checks supported:
tsx
<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:
js
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
自定义字体:Uniwind仅将className映射到font-family——字体文件必须单独加载(在
app.json
中使用expo-font插件,或在纯RN项目中使用
react-native-asset
)。
@theme
中的字体系列名称必须与文件名完全匹配(不含扩展名)。使用平台媒体查询实现跨平台字体:
css
@media ios { --font-sans: 'SF Pro Text'; }
@media android { --font-sans: 'Roboto-Regular'; }
数据选择器:使用
data-[prop=value]:utility
实现基于属性的样式。仅支持相等性检查:
tsx
<View data-state={isOpen ? 'open' : 'closed'} className="data-[state=open]:bg-muted/50" />
Expo Router中的global.css位置:放在项目根目录并在根布局(
app/_layout.tsx
)中导入。如果放在
app/
中,外部组件需要
@source
指令。Tailwind从
global.css
所在位置开始扫描。
CSS变更导致的全应用重载:Metro无法热重载包含多个提供者的文件。将
global.css
的导入移至组件树的更深处(如导航根或首页)以解决此问题。
渐变:内置支持,无需额外依赖。使用
bg-gradient-to-r from-red-500 via-yellow-500 to-green-500
。对于
expo-linear-gradient
,使用
useCSSVariable
获取颜色——
withUniwind
无法工作,因为渐变属性是数组。
样式优先级:内联
style
始终覆盖
className
className
用于静态样式,内联样式仅用于真正动态的值。避免为同一属性同时使用两者。
序列化错误
Failed to serialize javascript object
):清除缓存:
watchman watch-del-all 2>/dev/null; rm -rf node_modules/.cache && npx expo start --clear
。常见原因:复杂的
@theme
配置、CSS变量循环引用。
Metro unstable_enablePackageExports冲突:某些应用(如加密类)禁用此功能,导致Uniwind无法工作。使用选择性解析器:
js
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);
};
安全区域类
p-safe
pt-safe
pb-safe
px-safe
py-safe
m-safe
mt-safe
等。还支持
-or-{value}
(最小间距)和
-offset-{value}
(额外间距)变体。
Next.js:官方不支持。Uniwind适用于Metro和Vite。社区插件:
uniwind-plugin-next
。对于Next.js,使用标准Tailwind CSS并共享设计令牌。
Vite:从v1.2.0开始支持。将
uniwind/vite
插件与
@tailwindcss/vite
一起使用。
UI套件:HeroUI Native、react-native-reusables和Gluestack 4.1+与Uniwind配合良好

Known Issues & Gotchas

已知问题与注意事项

  1. data- attributes*: Uniwind supports
    data-[prop=value]:utility
    syntax for conditional styling, similar to NativeWind.
  2. Animated styles: Migrate NativeWind animated classes to
    react-native-reanimated
    directly. Uniwind Pro has built-in Reanimated support.
  1. *data-属性:Uniwind支持
    data-[prop=value]:utility
    语法实现条件样式,与NativeWind类似。
  2. 动画样式:将NativeWind动画类迁移为直接使用
    react-native-reanimated
    。Uniwind Pro内置Reanimated支持。

Verification

验证

After migration, verify:
  1. npx react-native start --reset-cache
    (clear Metro cache) or with expo
    npx expo start -c
  2. All screens render correctly on iOS and Android
  3. Theme switching works (light/dark)
  4. Custom fonts load correctly
  5. Safe area insets apply properly
  6. No console warnings about missing styles
  7. 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
迁移完成后,验证以下内容:
  1. npx react-native start --reset-cache
    (清除Metro缓存),或使用Expo的
    npx expo start -c
  2. 所有屏幕在iOS和Android上都能正确渲染
  3. 主题切换正常工作(浅色/深色)
  4. 自定义字体加载正常
  5. 安全区域内边距正确应用
  6. 没有关于缺失样式的控制台警告
  7. 没有来自
    nativewind
    react-native-css-interop
    的剩余导入
重要提示:不要猜测Uniwind API。如果对任何Uniwind API、钩子、组件或配置选项有疑问,请查阅并验证官方文档:https://docs.uniwind.dev/llms-full.txt