mcpField
U
uniwind

uniwind

github.com/hairyf设计与 UI2641 次安装2 周前更新
代码仓库

关于 uniwind

Uniwind 将 Tailwind CSS v4 样式引入 React Native 项目,供使用 React Native 和 Expo 构建应用的开发者使用。它在构建时编译样式,并让核心组件支持 `className`,无需运行时样式处理。

主要功能

  1. 01为 React Native 提供 Tailwind CSS v4 工具类
  2. 02构建时编译样式
  3. 03核心组件支持 `className`
  4. 04通过 CSS 变量配置主题
  5. 05支持 Metro 和 Vite 配置

适用场景

  1. 01用工具类为 React Native 组件设定样式
  2. 02在应用组件间复用 Tailwind 样式
  3. 03为 React Native 界面配置主题

安装

复制下面的命令,在终端中运行。

$npx skills add https://github.com/hairyf/skills --skill uniwind

安装后在 Claude Code 中输入/uniwind确认已加载。

附带文件LICENSE.mdSYNC.md

详细介绍

Uniwind — 完整参考

Uniwind 1.7.0+ / Uniwind Pro 1.2.1+ / Tailwind CSS v4 / React Native 0.81+ / Expo SDK 54+

如果用户的版本较低,建议更新到 1.7.0+(免费版)/ 1.2.1+(Pro 版)以获得最佳体验。

Uniwind 将 Tailwind CSS v4 引入 React Native。所有核心 React Native 组件开箱即支持 className 属性。样式在构建时编译 —— 无运行时开销。

关键规则

  1. 仅支持 Tailwind v4 —— 使用 @import 'tailwindcss',而不是 @tailwind base。不支持 Tailwind v3。
  2. 切勿动态构造 className —— Tailwind 在构建时扫描。bg-${color}-500 不会生效。请使用完整的字符串字面量、映射对象或三元表达式。
  3. 切勿使用 cssInterop 或 remapProps —— 这些是 NativeWind 的 API。Uniwind 不会覆盖全局组件。
  4. 没有 tailwind.config.js —— 所有配置都通过 @theme 和 @layer theme 放在 global.css 中。
  5. 无需 ThemeProvider —— 直接使用 Uniwind.setTheme()。
  6. withUniwindConfig 必须是最外层的 Metro 配置包装器。
  7. 切勿用 withUniwind 包装 react-native 或 react-native-reanimated 组件 —— View、Text、Pressable、Image、TextInput、ScrollView、FlatList、Switch、Modal、Animated.View、Animated.Text 等已内置完整的 className 支持。用 withUniwind 包装它们会破坏行为。仅对第三方组件(例如 expo-image、expo-blur、moti)使用 withUniwind。
  8. 字体族:仅支持单一字体 —— React Native 不支持回退字体。使用 --font-sans: 'Roboto-Regular',而不是 'Roboto', sans-serif。
  9. 所有主题变体必须定义相同的 CSS 变量集合 —— 如果 light 定义了 --color-primary,那么 dark 和每个自定义主题也必须定义。变量不匹配会导致运行时错误。
  10. 非样式颜色属性必须使用 accent- 前缀 —— 这一点至关重要。诸如 color(Button、ActivityIndicator)、tintColor(Image)、thumbColor(Switch)、placeholderTextColor(TextInput)等属性不属于 style 对象。你必须使用对应的 {propName}ClassName 属性并搭配 accent- 前缀的类。示例:<ActivityIndicator colorClassName="accent-blue-500" />,而不是 <ActivityIndicator className="text-blue-500" />。常规的 Tailwind 颜色类(如 text-blue-500)仅对 className(映射到 style)生效。对于非样式颜色属性,始终使用 accent-。
  11. rem 默认值是 16px —— NativeWind 使用的是 14px。迁移时请在 metro 配置中设置 polyfills: { rem: 14 }。
  12. cssEntryFile 必须是相对路径字符串 —— 使用 './global.css',而不是 path.resolve(__dirname, 'global.css')。
  13. 混合自定义 CSS 类和 Tailwind 时用 cn() 去重 —— Uniwind 不会自动去重。如果某个自定义 CSS 类(.card { padding: 16px })和某个 Tailwind 工具类(p-6)设置了相同的属性,两者都会生效,结果不可预测。存在重叠时,始终用 cn('card', 'p-6') 包装。
  14. 支持 important 工具类 —— Tailwind 的 important 修饰符可在 classNames 中使用,! 放在末尾:bg-red-500!、active:bg-red-500!、ios:pt-12!。前置的 !bg-red-500 语法已弃用。Important 工具类会覆盖针对同一样式属性的非 important 工具类,但内联 style 仍会覆盖 className。

设置

安装
# or other package manager
bun install uniwind tailwindcss

需要 Tailwind CSS v4+。

global.css

创建一个 CSS 入口文件:

@import 'tailwindcss';
@import 'uniwind';

在你的 App 组件中导入(例如 App.tsx 或 app/_layout.tsx),切勿在 index.ts/index.js 中导入 —— 在那里导入会破坏热重载:

// app/_layout.tsx or App.tsx
import './global.css';

包含 global.css 的目录即应用根目录 —— Tailwind 从此目录开始扫描 className。

Metro 配置
const { getDefaultConfig } = require('expo/metro-config');
// Bare RN: const { getDefaultConfig } = require('@react-native/metro-config');
const { withUniwindConfig } = require('uniwind/metro');

const config = getDefaultConfig(__dirname);

// withUniwindConfig MUST be the OUTERMOST wrapper
module.exports = withUniwindConfig(config, {
  cssEntryFile: './global.css',           // Required — relative path from project root
  polyfills: { rem: 16 },                // Optional — base rem value (default 16)
  extraThemes: ['ocean', 'sunset'],       // Optional — custom themes beyond light/dark
  dtsFile: './uniwind-types.d.ts',        // Optional — TypeScript types output path
  debug: true,                            // Optional — log unsupported CSS in dev
  isTV: false,                            // Optional — enable TV platform support
});

对于大多数流程,保留默认值,仅提供 cssEntryFile。

包装器顺序 —— Uniwind 必须包装其他所有内容:

// CORRECT
module.exports = withUniwindConfig(withOtherConfig(config, opts), { cssEntryFile: './global.css' });

// WRONG — Uniwind is NOT outermost
module.exports = withOtherConfig(withUniwindConfig(config, { cssEntryFile: './global.css' }), opts);
Vite 配置(v1.2.0+)

如果用户配置了 storybook,请添加额外的 vite 配置:

import tailwindcss from '@tailwindcss/vite';
import { uniwind } from 'uniwind/vite';
import { defineConfig } from 'vite';

export default defineConfig({
  plugins: [
    tailwindcss(),
    uniwind({
      cssEntryFile: './src/global.css',
      dtsFile: './src/uniwind-types.d.ts',
    }),
  ],
});
TypeScript

Uniwind 在运行 Metro 后会自动生成一个 .d.ts 文件(默认:./uniwind-types.d.ts)。将其放在 src/ 或 app/ 中以实现自动包含,或添加到 tsconfig.json:

{ "include": ["./uniwind-types.d.ts"] }

如果用户遇到与 className 相关的 TypeScript 错误,只需运行 metro 服务以构建 d.ts 文件。

Expo Router 放置位置
project/
├── app/_layout.tsx    ← import '../global.css' here
├── components/
├── global.css         ← project root (best location)
└── metro.config.js    ← cssEntryFile: './global.css'

如果 global.css 位于 app/ 目录中,为同级目录添加 @source:

@import 'tailwindcss';
@import 'uniwind';
@source '../components';
Tailwind IntelliSense(VS Code / Cursor / Windsurf)
{
  "tailwindCSS.classAttributes": [
    "class", "className", "headerClassName",
    "contentContainerClassName", "columnWrapperClassName",
    "endFillColorClassName", "imageClassName", "tintColorClassName",
    "ios_backgroundColorClassName", "thumbColorClassName",
    "trackColorOnClassName", "trackColorOffClassName",
    "selectionColorClassName", "cursorColorClassName",
    "underlineColorAndroidClassName", "placeholderTextColorClassName",
    "selectionHandleColorClassName", "colorsClassName",
    "progressBackgroundColorClassName", "titleColorClassName",
    "underlayColorClassName", "colorClassName",
    "backdropColorClassName", "backgroundColorClassName",
    "statusBarBackgroundColorClassName", "drawerBackgroundColorClassName",
    "ListFooterComponentClassName", "ListHeaderComponentClassName"
  ],
  "tailwindCSS.classFunctions": ["useResolveClassNames"]
}
Monorepo 支持

在 global.css 中为 CSS 入口文件目录之外的包添加 @source 指令:

@import 'tailwindcss';
@import 'uniwind';
@source "../../packages/ui/src";
@source "../../packages/shared/src";

对于包含 Uniwind 类的 node_modules 包(例如共享 UI 库)同样需要。

组件绑定

所有核心 React Native 组件开箱即支持 className。部分组件为子样式提供了额外的 className 属性(如 contentContainerClassName)以及非样式颜色属性(需要 accent- 前缀)。

完整参考

图例:标记为 ⚡ 的属性需要 accent- 前缀。括号内的属性为平台特有。

View
属性映射到前缀
classNamestyle—
Text
属性映射到前缀
classNamestyle—
selectionColorClassNameselectionColor⚡ accent-
Pressable
属性映射到前缀
classNamestyle—

支持 active:、disabled:、focus: 状态选择器。

Image
属性映射到前缀
classNamestyle—
tintColorClassNametintColor⚡ accent-
TextInput
属性映射到前缀
classNamestyle—
cursorColorClassNamecursorColor⚡ accent-
selectionColorClassNameselectionColor⚡ accent-
placeholderTextColorClassNameplaceholderTextColor⚡ accent-
selectionHandleColorClassNameselectionHandleColor⚡ accent-
underlineColorAndroidClassNameunderlineColorAndroid(Android)⚡ accent-

支持 focus:、active:、disabled: 状态选择器。

ScrollView
属性映射到前缀
classNamestyle—
contentContainerClassNamecontentContainerStyle—
endFillColorClassNameendFillColor⚡ accent-
FlatList
属性映射到前缀
classNamestyle—
contentContainerClassNamecontentContainerStyle—
columnWrapperClassNamecolumnWrapperStyle—
ListHeaderComponentClassNameListHeaderComponentStyle—
ListFooterComponentClassNameListFooterComponentStyle—
endFillColorClassNameendFillColor⚡ accent-
SectionList
属性映射到前缀
classNamestyle—
contentContainerClassNamecontentContainerStyle—
ListHeaderComponentClassNameListHeaderComponentStyle—
ListFooterComponentClassNameListFooterComponentStyle—
endFillColorClassNameendFillColor⚡ accent-
VirtualizedList
属性映射到前缀
classNamestyle—
contentContainerClassNamecontentContainerStyle—
ListHeaderComponentClassNameListHeaderComponentStyle—
ListFooterComponentClassNameListFooterComponentStyle—
endFillColorClassNameendFillColor⚡ accent-
Switch
属性映射到前缀
thumbColorClassNamethumbColor⚡ accent-
trackColorOnClassNametrackColor.true(开启)⚡ accent-
trackColorOffClassNametrackColor.false(关闭)⚡ accent-
ios_backgroundColorClassNameios_backgroundColor(iOS)⚡ accent-

注意:Switch 不支持 className(类型中为 className?: never)。请仅使用上述颜色相关的 className 属性。支持 disabled: 状态选择器。

ActivityIndicator
属性映射到前缀
classNamestyle—
colorClassNamecolor⚡ accent-
Button
属性映射到前缀
colorClassNamecolor⚡ accent-

注意:Button 不支持 className(RN Button 没有 style 属性)。

Modal
属性映射到前缀
classNamestyle—
backdropColorClassNamebackdropColor⚡ accent-
RefreshControl
属性映射到前缀
classNamestyle—
colorsClassNamecolors(Android)⚡ accent-
tintColorClassNametintColor(iOS)⚡ accent-
titleColorClassNametitleColor(iOS)⚡ accent-
progressBackgroundColorClassNameprogressBackgroundColor(Android)⚡ accent-
ImageBackground
属性映射到前缀
classNamestyle—
imageClassNameimageStyle—
tintColorClassNametintColor⚡ accent-
SafeAreaView
属性映射到前缀
classNamestyle—
KeyboardAvoidingView
属性映射到前缀
classNamestyle—
contentContainerClassNamecontentContainerStyle—
InputAccessoryView
属性映射到前缀
classNamestyle—
backgroundColorClassNamebackgroundColor⚡ accent-
TouchableHighlight
属性映射到前缀
classNamestyle—
underlayColorClassNameunderlayColor⚡ accent-

支持 active:、disabled: 状态选择器。

TouchableOpacity
属性映射到前缀
classNamestyle—

支持 active:、disabled: 状态选择器。

TouchableNativeFeedback
属性映射到前缀
classNamestyle—

支持 active:、disabled: 状态选择器。

TouchableWithoutFeedback
属性映射到前缀
classNamestyle—

支持 active:、disabled: 状态选择器。

用法示例
import { View, Text, Pressable, TextInput, ScrollView, FlatList, Switch, Image, ActivityIndicator, Modal, RefreshControl, Button } from 'react-native';

// View — basic layout
<View className="flex-1 bg-background p-4">
  <Text className="text-foreground text-lg font-bold">Title</Text>
</View>

// Pressable — with press/focus states
<Pressable className="bg-primary px-6 py-3 rounded-lg active:opacity-80 active:bg-primary/90 focus:ring-2">
  <Text className="text-white text-center font-semibold">Press Me</Text>
</Pressable>

// TextInput — with focus state and accent- color props
<TextInput
  className="border border-border rounded-lg px-4 py-2 text-base text-foreground focus:border-primary"
  placeholderTextColorClassName="accent-muted"
  selectionColorClassName="accent-primary"
  cursorColorClassName="accent-primary"
  selectionHandleColorClassName="accent-primary"
  underlineColorAndroidClassName="accent-transparent"
  placeholder="Enter text..."
/>

// ScrollView — with content container
<ScrollView className="flex-1" contentContainerClassName="p-4 gap-4">
  {/* content */}
</ScrollView>

// FlatList — with all sub-style props
<FlatList
  className="flex-1"
  contentContainerClassName="p-4 gap-3"
  columnWrapperClassName="gap-3"
  ListHeaderComponentClassName="pb-4"
  ListFooterComponentClassName="pt-4"
  endFillColorClassName="accent-gray-100"
  numColumns={2}
  data={items}
  renderItem={({ item }) => <ItemCard item={item} />}
/>

// Switch — no className support, use color-specific props only
<Switch
  thumbColorClassName="accent-white"
  trackColorOnClassName="accent-primary"
  trackColorOffClassName="accent-gray-300 dark:accent-gray-700"
  ios_backgroundColorClassName="accent-gray-200"
/>

// Image — tint color
<Image className="w-6 h-6" tintColorClassName="accent-primary" source={icon} />

// ActivityIndicator
<ActivityIndicator className="m-4" colorClassName="accent-primary" size="large" />

// Button — only colorClassName (no className)
<Button colorClassName="accent-primary" title="Submit" onPress={handleSubmit} />

// Modal — backdrop color
<Modal className="flex-1" backdropColorClassName="accent-black/50">
  {/* content */}
</Modal>

// RefreshControl — platform-specific color props
<RefreshControl
  className="p-4"
  tintColorClassName="accent-primary"
  titleColorClassName="accent-gray-500"
  colorsClassName="accent-primary"
  progressBackgroundColorClassName="accent-white dark:accent-gray-800"
/>

// ImageBackground — separate image styling
<ImageBackground
  className="flex-1 justify-center items-center"
  imageClassName="opacity-50"
  tintColorClassName="accent-blue-500"
  source={bgImage}
>
  <Text className="text-white text-2xl font-bold">Overlay</Text>
</ImageBackground>

// KeyboardAvoidingView
<KeyboardAvoidingView
  behavior="padding"
  className="flex-1 bg-white"
  contentContainerClassName="p-4 justify-end"
>
  <TextInput className="border border-gray-300 rounded-lg p-3" placeholder="Type..." />
</KeyboardAvoidingView>

// InputAccessoryView
<InputAccessoryView
  className="p-4 border-t border-gray-300"
  backgroundColorClassName="accent-white dark:accent-gray-800"
>
  <Button title="Done" onPress={dismissKeyboard} />
</InputAccessoryView>

// TouchableHighlight — underlay color
<TouchableHighlight
  className="bg-blue-500 px-6 py-3 rounded-lg"
  underlayColorClassName="accent-blue-600 dark:accent-blue-700"
  onPress={handlePress}
>
  <Text className="text-white font-semibold">Press Me</Text>
</TouchableHighlight>

accent- 前缀模式

React Native 组件有一些不属于 style 对象的属性,例如 color、tintColor、thumbColor。要通过 Tailwind 类设置这些属性,请使用 accent- 前缀搭配相应的 {propName}ClassName 属性:

// color prop → colorClassName with accent- prefix
<ActivityIndicator colorClassName="accent-blue-500 dark:accent-blue-400" />
<Button colorClassName="accent-primary" title="Submit" />

// tintColor prop → tintColorClassName
<Image className="w-6 h-6" tintColorClassName="accent-red-500" source={icon} />

// thumbColor → thumbColorClassName
<Switch thumbColorClassName="accent-white" trackColorOnClassName="accent-primary" />

// placeholderTextColor → placeholderTextColorClassName
<TextInput placeholderTextColorClassName="accent-gray-400 dark:accent-gray-600" />

关键规则:className 映射到 style 属性——它负责布局、排版、背景、边框等。但 React Native 有许多位于 style 之外的颜色属性(如 color、tintColor、thumbColor、placeholderTextColor)。这些属性需要单独的 {propName}ClassName 属性并配合 accent- 前缀。如果没有 accent-,该类会解析为样式对象——但这些属性期望的是纯颜色字符串。

// WRONG — className sets style, but ActivityIndicator's color is NOT a style prop
<ActivityIndicator className="text-blue-500" />  // color will NOT be set

// CORRECT — use the dedicated colorClassName prop with accent- prefix
<ActivityIndicator colorClassName="accent-blue-500" />  // color IS set to #3b82f6

// WRONG — tintColor is not a style prop on Image
<Image className="tint-blue-500" source={icon} />  // won't work

// CORRECT
<Image tintColorClassName="accent-blue-500" source={icon} />

为第三方组件设置样式

withUniwind(推荐)

在模块级别包装一次,然后在各处使用 className:

import { withUniwind } from 'uniwind';
import { Image as ExpoImage } from 'expo-image';
import { BlurView as RNBlurView } from 'expo-blur';
import { LinearGradient as RNLinearGradient } from 'expo-linear-gradient';

// Module-level wrapping (NEVER inside render functions)
export const Image = withUniwind(ExpoImage);
export const BlurView = withUniwind(RNBlurView);
export const LinearGradient = withUniwind(RNLinearGradient);

withUniwind 会自动映射:

  • style → className
  • {name}Style → {name}ClassName
  • {name}Color → {name}ColorClassName(带 accent- 前缀)

自定义 prop 映射:

const StyledProgressBar = withUniwind(ProgressBar, {
  width: {
    fromClassName: 'widthClassName',
    styleProperty: 'width',
  },
});

使用模式:

  • 仅在一个文件中使用 — 在同一文件中定义包装后的组件
  • 在多个文件中使用 — 在共享模块(例如 components/styled.ts)中包装一次并重新导出
// components/styled.ts
import { withUniwind } from 'uniwind';
import { Image as ExpoImage } from 'expo-image';
export const Image = withUniwind(ExpoImage);

// Then import everywhere:
import { Image } from '@/components/styled';

绝不在多个文件中对同一组件调用 withUniwind。

关键:不要对来自 react-native 或 react-native-reanimated 的组件使用 withUniwind。这些组件已经内置了 className 支持:

// WRONG — View already supports className natively
const StyledView = withUniwind(View);        // DO NOT DO THIS
const StyledText = withUniwind(Text);        // DO NOT DO THIS
const StyledAnimatedView = withUniwind(Animated.View); // DO NOT DO THIS

// CORRECT — only wrap third-party components
const StyledExpoImage = withUniwind(ExpoImage);     // expo-image
const StyledBlurView = withUniwind(BlurView);        // expo-blur
const StyledMotiView = withUniwind(MotiView);        // moti
useResolveClassNames

将 Tailwind 类字符串转换为 React Native 样式对象。用于一次性场景或仅接受 style 的组件:

import { useResolveClassNames } from 'uniwind';

const headerStyle = useResolveClassNames('bg-primary p-4');
const cardStyle = useResolveClassNames('bg-card dark:bg-card rounded-lg shadow-sm');

// React Navigation screen options
<Stack.Navigator screenOptions={{ headerStyle, cardStyle }} />
对比
特性withUniwinduseResolveClassNames
设置每个组件一次每次使用时
性能已优化稍慢
最适合可复用组件一次性场景、导航配置
语法className="..."style={...}

动态 ClassName

绝不要这样做(Tailwind 在构建时扫描)
// BROKEN — template literal with variable
<View className={`bg-${color}-500`} />
<Text className={`text-${size}`} />
正确的模式
// Ternary with complete class names
<View className={isActive ? 'bg-primary' : 'bg-muted'} />

// Mapping object
const colorMap = {
  primary: 'bg-blue-500 text-white',
  danger: 'bg-red-500 text-white',
  ghost: 'bg-transparent text-foreground',
};
<Pressable className={colorMap[variant]} />

// Array join for multiple conditions
<View className={[
  'p-4 rounded-lg',
  isActive && 'bg-primary',
  isDisabled && 'opacity-50',
].filter(Boolean).join(' ')} />

tailwind-variants(tv)

用于带有变体和复合变体的复杂组件样式:

import { tv } from 'tailwind-variants';

const button = tv({
  base: 'font-semibold rounded-lg px-4 py-2 items-center justify-center',
  variants: {
    color: {
      primary: 'bg-blue-500 text-white',
      secondary: 'bg-gray-500 text-white',
      danger: 'bg-red-500 text-white',
      ghost: 'bg-transparent text-foreground border border-border',
    },
    size: {
      sm: 'text-sm px-3 py-1.5',
      md: 'text-base px-4 py-2',
      lg: 'text-lg px-6 py-3',
    },
    disabled: {
      true: 'opacity-50',
    },
  },
  compoundVariants: [
    { color: 'primary', size: 'lg', class: 'bg-blue-600' },
  ],
  defaultVariants: { color: 'primary', size: 'md' },
});

<Pressable className={button({ color: 'primary', size: 'lg' })}>
  <Text className="text-white font-semibold">Click</Text>
</Pressable>

cn 工具 — 类去重

Uniwind 不会自动去重冲突的 className。这意味着如果同一属性出现在多个类中,两者都会被应用,结果不可预测。当将自定义 CSS 类与 Tailwind 工具类混用时,这一点尤其关键。

设置
npm install tailwind-merge clsx
// lib/cn.ts
import { type ClassValue, clsx } from 'clsx';
import { twMerge } from 'tailwind-merge';

export function cn(...inputs: ClassValue[]) {
  return twMerge(clsx(inputs));
}
何时需要 cn
  1. 合并 className props — 组件接受可能冲突的外部 className:
import { cn } from '@/lib/cn';

<View className={cn('p-4 bg-white', props.className)} />
<Text className={cn('text-base', isActive && 'text-primary', disabled && 'opacity-50')} />
  1. 关键:将自定义 CSS 类与 Tailwind 工具类混用 — 如果你的自定义 CSS 类设置了某个属性,而某个 Tailwind 工具类也设置了该属性,则必须使用 cn() 去重:
/* global.css */
.card {
  background-color: white;
  border-radius: 12px;
  padding: 16px;
}
// WRONG — both .card (padding: 16px) and p-6 (padding: 24px) apply, result is unpredictable
<View className="card p-6" />

// CORRECT — cn deduplicates, p-6 wins over .card's padding
<View className={cn('card', 'p-6')} />
  1. tv() 输出与额外类组合 — tv 已经处理了自己的变体,但如果你在此基础上添加更多类:
<Pressable className={cn(button({ color: 'primary' }), props.className)} />
何时不需要 cn
  • 无冲突的静态 className:<View className="flex-1 p-4 bg-white" />
  • 无重叠 Tailwind 的单个自定义 CSS 类:<View className="card-shadow mt-4" />(如果 card-shadow 只设置 box-shadow,且没有 Tailwind 类也设置它)

重要工具类与样式优先级

Uniwind 支持 Tailwind 的重要修饰符(!),用于必须覆盖同一样式属性上另一个工具类的工具类。

import { View, Pressable } from 'react-native';

// bg-red-500! has higher priority than bg-blue-500
<View className="bg-blue-500 bg-red-500!" />;

// Important utilities work with state and platform variants
<Pressable className="bg-blue-500 active:bg-red-500!" />;
<View className="pt-4 ios:pt-12! android:pt-8!" />;

优先级规则:

  • 重要工具类(bg-red-500!)在同一属性上覆盖非重要工具类(bg-blue-500)。
  • 重要变体正常工作:active:bg-red-500!、ios:pt-12!、dark:text-white!。
  • 内联 style 始终优先,即使面对重要的 className 工具类:<View className="bg-red-500!" style={{ backgroundColor: 'blue' }} /> 渲染为蓝色。
  • 谨慎使用 !。对于可复用组件和使用方覆盖,优先使用 cn() 搭配 tailwind-merge。

主题

快速设置(dark: 前缀)

立即可用 — 无需配置:

<View className="bg-white dark:bg-gray-900">
  <Text className="text-black dark:text-white">Themed</Text>
</View>

最适合小型应用和原型设计。无法扩展到自定义主题。

可扩展设置(CSS 变量)

在 global.css 中定义,在各地使用,无需 dark: 前缀:

@layer theme {
  :root {
    @variant light {
      --color-background: #ffffff;
      --color-foreground: #111827;
      --color-foreground-secondary: #6b7280;
      --color-card: #ffffff;
      --color-border: #e5e7eb;
      --color-muted: #9ca3af;
      --color-primary: #3b82f6;
      --color-danger: #ef4444;
      --color-success: #10b981;
    }
    @variant dark {
      --color-background: #000000;
      --color-foreground: #ffffff;
      --color-foreground-secondary: #9ca3af;
      --color-card: #1f2937;
      --color-border: #374151;
      --color-muted: #6b7280;
      --color-primary: #3b82f6;
      --color-danger: #ef4444;
      --color-success: #10b981;
    }
  }
}
// Auto-adapts to current theme — no dark: prefix needed
<View className="bg-card border border-border p-4 rounded-lg">
  <Text className="text-foreground text-lg font-bold">Title</Text>
  <Text className="text-muted mt-2">Subtitle</Text>
</View>

变量命名:--color-background → bg-background、text-background。

优先使用 CSS 变量而非显式的 dark: 变体 — 它们更简洁、更易维护,并自动兼容自定义主题。

自定义主题

第 1 步 — 在 global.css 中定义:

@layer theme {
  :root {
    @variant light { /* ... */ }
    @variant dark { /* ... */ }
    @variant ocean {
      --color-background: #0c4a6e;
      --color-foreground: #e0f2fe;
      --color-primary: #06b6d4;
      --color-card: #0e7490;
      --color-border: #155e75;
      /* Must define ALL the same variables as light/dark */
    }
  }
}

第 2 步 — 在 metro.config.js 中注册(排除 light/dark — 它们是自动的):

module.exports = withUniwindConfig(config, {
  cssEntryFile: './global.css',
  extraThemes: ['ocean'],
});

添加主题后重启 Metro。

第 3 步 — 使用:

Uniwind.setTheme('ocean');
主题 API
import { Uniwind, useUniwind } from 'uniwind';

// Imperative (no re-render)
Uniwind.setTheme('dark');          // Force dark
Uniwind.setTheme('light');         // Force light
Uniwind.setTheme('system');        // Follow device (re-enables adaptive themes)
Uniwind.setTheme('ocean');         // Custom theme (must be in extraThemes)
Uniwind.currentTheme;              // Current theme name
Uniwind.hasAdaptiveThemes;         // true if following system

// Reactive hook (re-renders on change)
const { theme, hasAdaptiveThemes } = useUniwind();

Uniwind.setTheme('light') / setTheme('dark') 也会调用 Appearance.setColorScheme 以同步原生组件(Alert、Modal、系统对话框)。

默认情况下,Uniwind 使用 "system" 主题——跟随设备配色方案。如果用户想要覆盖它,只需 调用 Uniwind.setTheme 并传入所需的主题。这可以在 React 组件之上完成,以避免在运行时切换主题。

主题切换器示例
import { View, Pressable, Text, ScrollView } from 'react-native';
import { Uniwind, useUniwind } from 'uniwind';

export const ThemeSwitcher = () => {
  const { theme, hasAdaptiveThemes } = useUniwind();
  const activeTheme = hasAdaptiveThemes ? 'system' : theme;

  const themes = [
    { name: 'light', label: 'Light' },
    { name: 'dark', label: 'Dark' },
    { name: 'system', label: 'System' },
  ];

  return (
    <ScrollView horizontal showsHorizontalScrollIndicator={false}>
      <View className="flex-row gap-2 p-4">
        {themes.map((t) => (
          <Pressable
            key={t.name}
            onPress={() => Uniwind.setTheme(t.name)}
            className={`px-4 py-3 rounded-lg items-center ${
              activeTheme === t.name ? 'bg-primary' : 'bg-card border border-border'
            }`}
          >
            <Text className={`text-sm ${
              activeTheme === t.name ? 'text-white' : 'text-foreground'
            }`}>
              {t.label}
            </Text>
          </Pressable>
        ))}
      </View>
    </ScrollView>
  );
};
ScopedTheme

将不同的主题应用于子树,而不更改全局主题:

import { ScopedTheme } from 'uniwind';

<View className="gap-3">
  <PreviewCard />

  <ScopedTheme theme="dark">
    <PreviewCard />  {/* Renders with dark theme */}
  </ScopedTheme>

  <ScopedTheme theme="ocean">
    <PreviewCard />  {/* Renders with ocean theme */}
  </ScopedTheme>
</View>
  • 最近的 ScopedTheme 生效(支持嵌套作用域)
  • Hooks(useUniwind、useResolveClassNames、useCSSVariable)会针对最近的作用域主题进行解析
  • 作用域内被 withUniwind 包裹的组件也会解析作用域主题的值
  • 自定义主题需要在 extraThemes 中注册
useCSSVariable

在 JavaScript 中访问 CSS 变量值:

import { useCSSVariable } from 'uniwind';

const primaryColor = useCSSVariable('--color-primary');
const spacing = useCSSVariable('--spacing-4');

// Multiple variables at once
const [bg, fg] = useCSSVariable(['--color-background', '--color-foreground']) as [string, string]

用于:动画、图表库、第三方组件配置、使用设计令牌进行计算。

需要将 useCSSVariable 的结果进行类型转换,因为它可能返回:string | number | undefined。 Uniwind 不知道给定变量是否存在以及它是什么类型,因此它返回联合类型。

getCSSVariable

在 React 之外读取 CSS 变量值(事件处理器、异步回调、工具模块、worklets)。在 Uniwind 1.6.4+ 中可用。

import { Uniwind } from 'uniwind';

const primary = Uniwind.getCSSVariable('--color-primary');
const [bg, fg] = Uniwind.getCSSVariable(['--color-background', '--color-foreground']) as [string, string];

值规则与 useCSSVariable 相同(变量必须在 className 中使用,或在 @theme static 中声明)。返回类型也相同:string | number | undefined。按需进行类型转换。

非响应式——值只读取一次。对于组件内的响应式值,请使用 useCSSVariable。将 getCSSVariable 用于一次性读取(onPress 处理器、工具函数、原生模块配置)。

运行时 CSS 变量更新

在运行时更新主题变量(例如,用户选择的品牌颜色或由 API 驱动的主题):

Uniwind.updateCSSVariables('light', {
  '--color-primary': '#ff6600',
  '--color-background': '#fafafa',
});

更新是针对特定主题的,并立即生效。

@theme static

用于不在 classNames 中使用的仅限 JS 的值:

@theme static {
  --chart-line-width: 2;
  --chart-dot-radius: 4;
  --animation-duration: 300;
}

通过 useCSSVariable('--chart-line-width') 访问。用于:图表配置、动画时长、原生模块值。

OKLCH 颜色支持

感知均匀的颜色格式——更宽的色域、一致的光亮度:

@layer theme {
  :root {
    @variant light {
      --color-primary: oklch(0.5 0.2 240);
      --color-background: oklch(1 0 0);
    }
    @variant dark {
      --color-primary: oklch(0.6 0.2 240);
      --color-background: oklch(0.13 0.004 17.69);
    }
  }
}
Display P3 颜色支持

适用于支持 P3 色彩空间的设备(大多数现代 iPhone 和 Mac)的广色域颜色格式。Uniwind 解析 color(display-p3 ...) 值并将其转换为原生使用:

@layer theme {
  :root {
    @variant light {
      --color-primary: color(display-p3 0.2 0.4 1);
      --color-accent: color(display-p3 1 0.3 0.3);
    }
    @variant dark {
      --color-primary: color(display-p3 0.3 0.5 1);
      --color-accent: color(display-p3 1 0.4 0.4);
    }
  }
}

平台选择器

直接在 className 中应用平台特定的样式:

// Individual platforms
<View className="ios:bg-red-500 android:bg-blue-500 web:bg-green-500" />

// native: shorthand (iOS + Android)
<View className="native:bg-blue-500 web:bg-gray-500" />

// TV platforms
<View className="tv:p-8 android-tv:bg-black apple-tv:bg-gray-900" />

// Combine with other utilities
<View className="p-4 ios:pt-12 android:pt-6 web:pt-4" />

在 @layer theme 中使用平台变体来设置全局值(使用 @variant,而不是 @media):

@layer theme {
  :root {
    @variant ios { --font-sans: 'SF Pro Text'; }
    @variant android { --font-sans: 'Roboto-Regular'; }
    @variant web { --font-sans: 'Inter'; }
  }
}

优先使用平台选择器而不是 Platform.select() —— 语法更简洁,无需导入。

数据选择器

使用 data-[prop=value]:utility 根据 prop 值设置样式:

// Boolean props
<Pressable
  data-selected={isSelected}
  className="border rounded px-3 py-2 data-[selected=true]:ring-2 data-[selected=true]:ring-primary"
/>

// String props
<View
  data-state={isOpen ? 'open' : 'closed'}
  className="p-4 data-[state=open]:bg-muted/50 data-[state=closed]:bg-transparent"
/>

// Tabs pattern
<Pressable
  data-selected={route.key === current}
  className="px-4 py-2 rounded-md text-foreground/60
    data-[selected=true]:bg-primary data-[selected=true]:text-white"
>
  <Text>{route.title}</Text>
</Pressable>

// Toggle pattern
<Pressable
  data-checked={enabled}
  className="h-6 w-10 rounded-full bg-muted data-[checked=true]:bg-primary"
>
  <View className="h-5 w-5 rounded-full bg-background translate-x-0 data-[checked=true]:translate-x-4" />
</Pressable>

规则:

  • 仅支持相等选择器(data-[prop=value])
  • 不支持仅存在性选择器(data-[prop] —— 不支持)
  • 不支持 has-data-* 父选择器(React Native 中不支持)
  • 布尔值同时匹配布尔形式和字符串形式

交互状态

// active: — when pressed
<Pressable className="bg-primary active:bg-primary/80 active:opacity-90 active:scale-95">
  <Text className="text-white">Press me</Text>
</Pressable>

// disabled: — when disabled prop is true
<Pressable
  disabled={isLoading}
  className="bg-primary disabled:bg-gray-300 disabled:opacity-50"
>
  <Text className="text-white disabled:text-gray-500">Submit</Text>
</Pressable>

// focus: — keyboard/accessibility focus
<TextInput
  className="border border-border rounded-lg px-4 py-2 focus:border-primary focus:ring-2 focus:ring-primary/20"
/>

<Pressable className="bg-card rounded-lg p-4 focus:ring-2 focus:ring-primary">
  <Text className="text-foreground">Focusable</Text>
</Pressable>

支持状态的组件:

  • Pressable:active:、disabled:、focus:
  • TextInput:active:、disabled:、focus:
  • Switch:disabled:
  • Text:active:、disabled:
  • TouchableOpacity / TouchableHighlight / TouchableNativeFeedback / TouchableWithoutFeedback:active:、disabled:

响应式断点

移动优先 —— 无前缀样式适用于所有尺寸,带前缀样式在该断点及以上生效:

前缀最小宽度典型设备
(无)0px所有(移动端)
sm:640px大屏手机
md:768px平板
lg:1024px横屏平板
xl:1280px桌面端
2xl:1536px大屏桌面端
// Responsive padding and typography
<View className="p-4 sm:p-6 lg:p-8">
  <Text className="text-base sm:text-lg lg:text-xl font-bold">Responsive</Text>
</View>

// Responsive grid (1 col → 2 col → 3 col)
<View className="flex-row flex-wrap">
  <View className="w-full sm:w-1/2 lg:w-1/3 p-2">
    <View className="bg-card p-4 rounded"><Text>Item</Text></View>
  </View>
</View>

// Responsive visibility
<View className="hidden sm:flex flex-row gap-4">
  <Text>Visible on tablet+</Text>
</View>
<View className="flex sm:hidden">
  <Text>Mobile only</Text>
</View>

自定义断点:

@theme {
  --breakpoint-xs: 480px;
  --breakpoint-tablet: 820px;
  --breakpoint-3xl: 1920px;
}

用法:xs:p-2 tablet:p-4 3xl:p-8

采用移动优先设计 —— 从基础样式(无前缀)开始,再用断点增强:

// CORRECT — mobile-first
<View className="w-full sm:w-3/4 md:w-1/2 lg:w-1/3" />

// WRONG — desktop-first (reversed order is confusing and fragile)
<View className="w-full lg:w-1/2 md:w-3/4 sm:w-full" />

安全区域工具类

内边距
类名说明
p-safe所有边
pt-safe / pb-safe / pl-safe / pr-safe单边
px-safe / py-safe水平 / 垂直
外边距
类名说明
m-safe所有边
mt-safe / mb-safe / ml-safe / mr-safe单边
mx-safe / my-safe水平 / 垂直
定位
类名说明
inset-safe所有边
top-safe / bottom-safe / left-safe / right-safe单边
x-safe / y-safe水平 / 垂直 inset
复合变体
模式行为示例
{prop}-safe-or-{value}Math.max(inset, value) —— 确保最小间距pt-safe-or-4
{prop}-safe-offset-{value}inset + value —— 在 inset 基础上增加额外间距pb-safe-offset-4
设置

Uniwind Free(默认) —— 需要 react-native-safe-area-context 来更新 inset。 将你的 App 组件包裹在 SafeAreaProvider 和 SafeAreaListener 中,并在 onChange 回调中调用 Uniwind.updateInsets(insets):

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>
  );
}

Uniwind Pro —— 自动完成,无需设置。Inset 从原生层注入。

CSS 函数

Uniwind 提供用于设备感知和主题感知样式的 CSS 函数。它们可以在任何地方使用(自定义 CSS 类、@utility 等)—— 但不能在 @theme {} 内使用(后者只接受静态值)。使用 @utility 创建可复用的 Tailwind 风格工具类:

hairlineWidth()

返回设备可显示的最细线宽。用于细微的边框和分隔线。

@utility h-hairline { height: hairlineWidth(); }
@utility border-hairline { border-width: hairlineWidth(); }
@utility w-hairline { width: calc(hairlineWidth() * 10); }
<View className="h-hairline bg-gray-300" />
<View className="border-hairline border-gray-200 rounded-lg p-4" />
fontScale(multiplier?)

将基础值乘以设备的字体缩放无障碍设置。确保文本遵循用户对更大或更小字体的偏好。

  • fontScale() —— 使用乘数 1(设备字体缩放 × 1)
  • fontScale(0.9) —— 更小的缩放
  • fontScale(1.2) —— 更大的缩放
@utility text-sm-scaled { font-size: fontScale(0.9); }
@utility text-base-scaled { font-size: fontScale(); }
@utility text-lg-scaled { font-size: fontScale(1.2); }
<Text className="text-sm-scaled text-gray-600">Small accessible text</Text>
<Text className="text-base-scaled">Regular accessible text</Text>
pixelRatio(multiplier?)

将值乘以设备的像素比。创建跨屏幕密度缩放的像素级精准设计。

  • pixelRatio() —— 使用乘数 1(设备像素比 × 1)
  • pixelRatio(2) —— 像素比翻倍
@utility w-icon { width: pixelRatio(); }
@utility w-avatar { width: pixelRatio(2); }
<Image source={{ uri: 'avatar.png' }} className="w-avatar rounded-full" />
light-dark(lightValue, darkValue)

根据当前主题模式返回不同的值。主题变化时自动适配 —— 无需手动切换逻辑。

  • 第一个参数:浅色主题的值
  • 第二个参数:深色主题的值
@utility bg-adaptive { background-color: light-dark(#ffffff, #1f2937); }
@utility text-adaptive { color: light-dark(#111827, #f9fafb); }
@utility border-adaptive { border-color: light-dark(#e5e7eb, #374151); }
<View className="bg-adaptive border-adaptive border rounded-lg p-4">
  <Text className="text-adaptive">Adapts to light/dark theme</Text>
</View>

也可在自定义 CSS 类中使用(不仅限于 @utility):

.adaptive-card {
  background-color: light-dark(#ffffff, #1f2937);
  color: light-dark(#111827, #f9fafb);
}

自定义 CSS 与工具类

自定义 CSS 类

Uniwind 支持在 global.css 中定义自定义 CSS 类名。它们在构建时编译 —— 没有运行时开销。当你需要难以用 Tailwind 工具类表达的样式(例如复杂的 box-shadow、多属性组合)时使用它们。

/* global.css */
.card-shadow {
  background-color: white;
  border-radius: 12px;
  box-shadow: 0 2px 8px rgba(0, 0, 0, 0.08);
}

.adaptive-surface {
  background-color: light-dark(#ffffff, #1f2937);
  color: light-dark(#111827, #f9fafb);
}

.container {
  flex: 1;
  width: 100%;
  max-width: 1200px;
}

像任何 Tailwind 类一样通过 className 应用:

<View className="card-shadow" />
将自定义 CSS 与 Tailwind 混用

你可以在单个 className 中结合自定义 CSS 类与 Tailwind 工具类:

<View className="card-shadow p-4 m-2">
  <Text className="adaptive-surface mb-2">{title}</Text>
  <View className="container flex-row">{children}</View>
</View>

警告:如果自定义 CSS 类与 Tailwind 工具类设置了相同的属性,你必须使用 cn() 去重。不使用 cn() 时,两个值都会生效,结果不可预测:

// WRONG — .container sets flex:1, and flex-1 also sets flex:1 (harmless but wasteful)
// WRONG — .container sets width:100%, and w-full also sets width:100% (redundant)
// DANGEROUS — .card-shadow sets border-radius:12px, and rounded-2xl sets border-radius:16px — CONFLICT!
<View className="card-shadow rounded-2xl" />

// CORRECT — cn ensures rounded-2xl wins
import { cn } from '@/lib/cn';
<View className={cn('card-shadow', 'rounded-2xl')} />

经验法则:如果你的自定义 CSS 类设置的属性可能与你同样会使用的 Tailwind 工具类重叠,始终用 cn() 包裹。完整设置参见 cn 工具 章节。

自定义 CSS 指南
  • 保持选择器扁平 — 不要深层嵌套或使用子选择器
  • 对于简单的单属性样式,优先使用 Tailwind 工具类
  • 对于在 className 中会显得冗长的复杂或多属性组合,使用自定义类
  • 对主题感知的自定义类使用 light-dark()
  • 自定义类非常适合存放不符合 Tailwind 命名规范的共享设计令牌(例如 .card、.chip、.badge-dot)
自定义工具类(@utility)

@utility 指令创建的实用类与内置 Tailwind 类的工作方式完全一致。三种主要用例:

1. 变量驱动的工具类(运行时注入的值)

创建一个工具类,其值来自通过 updateCSSVariables 在运行时注入的 CSS 变量。使用 @theme static 声明该变量,以便 Uniwind 在其更新之前就能追踪它:

/* global.css */
@theme static {
  --header-height: 0px;
}

@utility p-safe-header {
  padding-top: var(--header-height);
}

在运行时注入真实值(例如,来自 react-navigation 的布局事件):

import { Uniwind } from 'uniwind'

// e.g., inside a navigation layout listener
Uniwind.updateCSSVariables(Uniwind.currentTheme, {
  '--header-height': headerHeight,
})
<View className="p-safe-header flex-1" />
2. 全新的工具类(没有 Tailwind 等价物)

用于没有内置 Tailwind 类的样式:

@utility h-hairline { height: hairlineWidth(); }
@utility text-scaled { font-size: fontScale(); }
@utility card-shadow {
  box-shadow: 0 4px 6px -1px rgba(0, 0, 0, 0.1), 0 2px 4px -1px rgba(0, 0, 0, 0.06);
}

像任何 Tailwind 类一样使用:<View className="h-hairline card-shadow" />

3. 覆盖已有的 Tailwind 工具类

使用 @utility 完全替换某个内置类的行为。例如:让 border 始终使用 --color-primary:

@utility border {
  border-width: 1px;
  border-style: solid;
  border-color: var(--color-primary);
}

@theme 指令

在 global.css 中自定义 Tailwind 设计令牌:

@theme {
  /* Colors */
  --color-primary: #3b82f6;
  --color-brand-500: #3b82f6;
  --color-brand-900: #1e3a8a;

  /* Typography */
  --font-sans: 'Roboto-Regular';
  --font-sans-medium: 'Roboto-Medium';
  --font-sans-bold: 'Roboto-Bold';
  --font-mono: 'FiraCode-Regular';

  /* Spacing & sizing */
  --text-base: 15px;
  --spacing-4: 16px;
  --radius-lg: 12px;

  /* Breakpoints */
  --breakpoint-tablet: 820px;
}

用法:bg-brand-500、text-brand-900、font-sans、font-mono、rounded-lg。

字体

React Native 每个字族需要单一字体 — 不支持回退:

@theme {
  --font-sans: 'Roboto-Regular';
  --font-sans-bold: 'Roboto-Bold';
  --font-mono: 'FiraCode-Regular';
}

字体名称必须完全匹配字体文件名(不含扩展名)。

Expo:在 app.json 中通过 expo-font 插件配置字体,然后在 CSS 中引用。

裸 RN:使用 react-native-asset 链接字体,CSS 配置相同。

平台特定字体(使用 @variant,而非 @media):

@layer theme {
  :root {
    @variant ios { --font-sans: 'SF Pro Text'; }
    @variant android { --font-sans: 'Roboto-Regular'; }
    @variant web { --font-sans: 'system-ui'; }
  }
}

渐变

内置支持 — 无需额外依赖:

<View className="bg-gradient-to-r from-red-500 via-yellow-500 to-green-500 p-4 rounded-lg">
  <Text className="text-white font-bold">Gradient</Text>
</View>

对于 expo-linear-gradient,你可以用 withUniwind 包裹它,以实现基于 className 的布局与样式(padding、border-radius、flex 等),但 colors 属性是一个数组,无法通过 className 解析 — 必须显式提供。使用 useCSSVariable 获取主题感知的颜色:

import { useCSSVariable } from 'uniwind';
import { withUniwind } from 'uniwind';
import { LinearGradient as RNLinearGradient } from 'expo-linear-gradient';

const LinearGradient = withUniwind(RNLinearGradient);

function GradientCard() {
  // useCSSVariable returns string | number | undefined
  const primary = useCSSVariable('--color-primary');
  const secondary = useCSSVariable('--color-secondary');

  // Guard against undefined — LinearGradient.colors requires valid ColorValues
  if (!primary || !secondary) {
    return null;
  }

  return (
    <LinearGradient
      className="flex-1 rounded-2xl p-6"
      colors={[primary as string, secondary as string]}
    >
      <Text className="text-white font-bold">Themed gradient</Text>
    </LinearGradient>
  );
}

或者,从共享模块导出一个包裹后的组件以便复用:

// components/styled.ts
import { withUniwind } from 'uniwind';
import { LinearGradient as RNLinearGradient } from 'expo-linear-gradient';

export const LinearGradient = withUniwind(RNLinearGradient);
// usage — className handles layout, colors still passed manually
import { LinearGradient } from '@/components/styled';

<LinearGradient className="rounded-xl p-4" colors={['#ff6b6b', '#4ecdc4']}>
  <Text className="text-white">Static gradient</Text>
</LinearGradient>

React Navigation 集成

对于只接受 style 对象的屏幕选项,使用 useResolveClassNames:

import { useResolveClassNames } from 'uniwind';

function Layout() {
  const headerStyle = useResolveClassNames('bg-background');
  const headerTitleStyle = useResolveClassNames('text-foreground font-bold');

  return (
    <Stack.Navigator
      screenOptions={{
        headerStyle,
        headerTitleStyle,
      }}
    />
  );
}

如果已经在使用 React Navigation 的 <ThemeProvider>,请保留它 — 它管理导航特定的主题。

UI 套件兼容性

  • HeroUI Native:可与 Uniwind 配合使用。内部使用 tailwind-variants(tv)。直接在 HeroUI 组件上应用 className。Bun 用户:Bun 对 node_modules 使用符号链接,这可能导致 Tailwind 的 Oxide 扫描器在生产构建中遗漏库类。修复方法:在 @source 中使用解析后的路径并提升该包:
    @source "../../node_modules/heroui-native/lib";
    
    # .npmrc
    public-hoist-pattern[]=heroui-native
    
  • react-native-reusables:兼容。
  • Gluestack v4.1+:兼容。
  • Lucide React Native:使用 withUniwind(LucideIcon) 并配合 colorClassName="accent-blue-500" 设置图标颜色。适用于所有 Lucide 图标。
  • @shopify/flash-list:使用 withUniwind(FlashList) 以支持 className 和 contentContainerClassName。注意:withUniwind 会丢失 ref 上的泛型类型参数 — 如有需要请手动转换。

使用语义化颜色令牌(bg-primary、text-foreground)以在各 UI 套件之间保持主题一致性。

支持的类与不支持的类

React Native 使用 Yoga 布局引擎。与 Web CSS 的关键区别:

  • 没有 CSS 级联/继承 — 样式不会从父级继承
  • 默认使用 Flexbox — 所有视图都使用 flexbox,且 flexDirection: 'column'
  • CSS 属性有限 — 没有 float、grid、伪元素
内置额外工具类

Uniwind 为标准 Tailwind 未覆盖的 React Native 特性提供了额外的工具类:

类效果
border-continuous设置 borderCurve: 'continuous' — 平滑的超椭圆圆角(iOS)
border-circular设置 borderCurve: 'circular' — 标准的圆形圆角(iOS 默认)
// Smooth iOS-style rounded corners (like SwiftUI's .continuous)
<View className="rounded-2xl border-continuous bg-card p-4">
  <Text className="text-foreground">Smooth corners</Text>
</View>
支持(所有标准 Tailwind)

布局、间距、尺寸、排版、颜色、边框、效果、flexbox、定位、变换、交互状态。

不支持(Web 专属,会被静默忽略)
  • hover: visited: — 请改用 Pressable 的 active:
  • before: after: placeholder: — 伪元素
  • float-* clear-* columns-*
  • print: screen:
  • break-before-* break-after-* break-inside-*

Uniwind Pro

付费升级版,100% API 兼容。基于第二代 C++ 引擎构建,面向对性能有极致要求的应用。阶梯定价(按年计费):$99/席位(1-3),$49(4-6),$29(7-15),$1(16+)。价格与许可:https://uniwind.dev/pricing

价格与许可
  • 按席位阶梯定价(按年计费,除非适用否则不含 VAT):1-3 个席位 $99,4-6 个 $49,7-15 个 $29,16+ 个 $1
  • 个人许可:每位工程师的个人 Pro 许可
  • 团队许可:单一密钥管理 — 可即时添加或移除成员
  • CI/CD 许可:全面支持自动化与无头构建环境
  • 企业版:可提供定制方案(联系 [email protected])
  • 优先支持:关键问题按优先响应时间解决
概述
  • C++ 样式引擎:基于第二代 Unistyles C++ 引擎打造。直接将样式注入 ShadowTree,而不触发 React 重新渲染 — 在 classNames 与原生层之间建立一条直接、优化的高速通道
  • 性能:基准测试约为 ~55ms(对比 StyleSheet 49ms、传统 Uniwind 81ms、NativeWind 197ms)— 接近原生速度
  • 55+ 个 className 属性在 20 个组件中更新时无需重新渲染(所有组件绑定均列于上方)
  • Reanimated 动画:通过 className 使用 animate-* 和 transition-*(Reanimated v4)
  • 原生 insets 与运行时值:自动注入安全区域、设备旋转与字体大小更新 — 无需设置 SafeAreaListener
  • 主题过渡:切换主题时使用原生动画过渡(淡入淡出、滑动、圆形遮罩)
  • 组变体:Tailwind group-active:* / group-focus:* 通过 C++ shadow tree 传播父级交互状态,零重新渲染
  • 默认样式:实验性 1.2.0+ 功能,可通过 CSS 选择器(如 View { ... } 和 Text { ... })为默认 React Native 组件设置样式
  • 优先支持:别让技术障碍拖慢你的团队

包:在 package.json 中使用 "uniwind": "npm:uniwind-pro@latest"。

安装
  1. 在 package.json 中设置依赖别名:

    { "dependencies": { "uniwind": "npm:uniwind-pro@latest" } }
    
  2. 安装 peer 依赖:

    npm install react-native-nitro-modules react-native-reanimated react-native-worklets
    
  3. 进行身份验证:npx uniwind-pro(交互式 — 选择 "Login with GitHub")

  4. 添加 Babel 插件 — 将 react-native-worklets/plugin 追加到现有的 plugins 数组中。请勿替换你的 presets(Expo 请保留 babel-preset-expo,或保留你的裸 RN preset):

    // babel.config.js
    module.exports = {
      // your existing presets and config — leave untouched
      plugins: [
        // ...other plugins
        'react-native-worklets/plugin',
      ],
    };
    

    如果你已经在使用 Reanimated,可能已经配置了 worklets 插件。

  5. 如有需要,将 postinstall 加入白名单:

    • bun:在 package.json 中添加 "trustedDependencies": ["uniwind"]
    • yarn v2+:在 .yarnrc.yml 中配置
    • pnpm:pnpm config set enable-pre-post-scripts true
  6. 重新构建原生应用:

    npx expo prebuild --clean && npx expo run:ios
    

Pro 不适用于 Expo Go。需要重新构建原生应用。

验证安装:检查 node_modules/uniwind 中是否存在原生模块(.cpp、.mm 文件)。

Reanimated 动画(需要 Reanimated v4.0.0+)
<View className="size-32 bg-primary rounded animate-spin" />
<View className="size-32 bg-primary rounded animate-bounce" />
<View className="size-32 bg-primary rounded animate-pulse" />
<View className="size-32 bg-primary rounded animate-ping" />

// Loading spinner
<View className="size-8 border-4 border-gray-200 border-t-blue-500 rounded-full animate-spin" />

超越 Tailwind 默认值的自定义关键帧动画:

类描述
animate-wiggle旋转摆动
animate-shake水平抖动
animate-flash不透明度闪烁开/关
animate-rubber-band弹性缩放拉伸
animate-swing钟摆式摇摆
animate-tada缩放 + 旋转的吸引注意力效果
animate-heartbeat双脉冲心跳
animate-jello旋转果冻晃动
animate-float轻柔垂直浮动
animate-breathe细微呼吸式缩放
animate-tilt交替倾斜旋转
animate-glitch快速水平抖动

当检测到动画类时,组件会自动切换为 Animated 版本:

组件Animated 版本
ViewAnimated.View
TextAnimated.Text
ImageAnimated.Image
ImageBackgroundAnimated.ImageBackground
ScrollViewAnimated.ScrollView
FlatListAnimated.FlatList
TextInputAnimated.TextInput
PressableAnimated.Pressable
进入与退出动画

通过 className 驱动 Reanimated 的进入/退出动画 —— 无需导入 Reanimated。当检测到 uw-* 类时,组件会自动升级。

// Bounce in, bounce out
{visible && <View className="size-20 bg-primary rounded-xl uw-entering-bounce-in uw-exiting-bounce-out" />}

// Fade in slowly (1000ms)
{visible && <View className="size-20 bg-primary rounded-xl uw-entering-fade-in uw-entering-duration-1000 uw-exiting-fade-out" />}

进入预设:uw-entering-fade-in uw-entering-fade-in-right uw-entering-fade-in-left uw-entering-fade-in-up uw-entering-fade-in-down uw-entering-slide-in-right uw-entering-slide-in-left uw-entering-slide-in-up uw-entering-slide-in-down uw-entering-zoom-in uw-entering-zoom-in-rotate uw-entering-zoom-in-left uw-entering-zoom-in-right uw-entering-zoom-in-up uw-entering-zoom-in-down uw-entering-zoom-in-easy-up uw-entering-zoom-in-easy-down uw-entering-bounce-in uw-entering-bounce-in-down uw-entering-bounce-in-up uw-entering-bounce-in-left uw-entering-bounce-in-right uw-entering-flip-in-x-up uw-entering-flip-in-x-down uw-entering-flip-in-y-left uw-entering-flip-in-y-right uw-entering-flip-in-easy-x uw-entering-flip-in-easy-y uw-entering-stretch-in-x uw-entering-stretch-in-y uw-entering-rotate-in-down-left uw-entering-rotate-in-down-right uw-entering-rotate-in-up-left uw-entering-rotate-in-up-right uw-entering-roll-in-left uw-entering-roll-in-right uw-entering-pinwheel-in uw-entering-light-speed-in-right uw-entering-light-speed-in-left

退出预设:uw-exiting-fade-out uw-exiting-fade-out-right uw-exiting-fade-out-left uw-exiting-fade-out-up uw-exiting-fade-out-down uw-exiting-slide-out-right uw-exiting-slide-out-left uw-exiting-slide-out-up uw-exiting-slide-out-down uw-exiting-zoom-out uw-exiting-zoom-out-rotate uw-exiting-zoom-out-left uw-exiting-zoom-out-right uw-exiting-zoom-out-up uw-exiting-zoom-out-down uw-exiting-zoom-out-easy-up uw-exiting-zoom-out-easy-down uw-exiting-bounce-out uw-exiting-bounce-out-down uw-exiting-bounce-out-up uw-exiting-bounce-out-left uw-exiting-bounce-out-right uw-exiting-flip-out-x-up uw-exiting-flip-out-x-down uw-exiting-flip-out-y-left uw-exiting-flip-out-y-right uw-exiting-flip-out-easy-x uw-exiting-flip-out-easy-y uw-exiting-stretch-out-x uw-exiting-stretch-out-y uw-exiting-rotate-out-down-left uw-exiting-rotate-out-down-right uw-exiting-rotate-out-up-left uw-exiting-rotate-out-up-right uw-exiting-roll-out-left uw-exiting-roll-out-right uw-exiting-pinwheel-out uw-exiting-light-speed-out-right uw-exiting-light-speed-out-left

动画修饰符(模式:uw-{entering|exiting|layout}-{modifier}):

  • 时长:uw-{type}-duration-75 uw-{type}-duration-100 ... uw-{type}-duration-1000 或任意值 uw-{type}-duration-{ms}
  • 延迟:uw-{type}-delay-75 ... uw-{type}-delay-1000 或任意值 uw-{type}-delay-{ms}
  • 缓动:uw-{type}-ease-linear uw-{type}-ease-in uw-{type}-ease-out uw-{type}-ease-in-out uw-{type}-ease-bounce
  • 弹簧:uw-{type}-springify uw-{type}-damping-{value} uw-{type}-stiffness-{value} uw-{type}-mass-{value}
布局过渡

当同级元素被添加或移除时,对位置/尺寸变化进行动画处理:

<View className="w-full gap-2">
  {items.map(item => (
    <View key={item.id} className={`h-14 ${item.color} rounded-xl uw-entering-fade-in uw-exiting-fade-out uw-layout-linear-transition`} />
  ))}
</View>
类描述
uw-layout-linear-transition平滑线性重新定位
uw-layout-fading-transition重新定位期间淡出
uw-layout-jumping-transition弹跳式跳转到新位置
uw-layout-curved-transition曲线路径重新定位
uw-layout-sequenced-transition按顺序重新定位
uw-layout-entry-exit-transition布局期间组合进入/退出
过渡

当 className 或状态变化时平滑地改变属性:

// Color transition on press
<Pressable className="bg-primary active:bg-red-500 transition-colors duration-300" />

// Opacity transition
<View className={`transition-opacity duration-500 ${visible ? 'opacity-100' : 'opacity-0'}`} />

// Transform transition
<Pressable className="active:scale-95 transition-transform duration-150" />

// All properties
<Pressable className="bg-primary px-6 py-3 rounded-lg active:scale-95 active:bg-primary/80 transition-all duration-150">
  <Text className="text-white font-semibold">Animated Button</Text>
</Pressable>
ClassProperties
transition-none无过渡
transition-all所有属性
transition-colors背景、边框、文字颜色
transition-opacity不透明度
transition-shadow盒阴影
transition-transform缩放、旋转、平移

时长:duration-75 duration-100 duration-150 duration-200 duration-300 duration-500 duration-700 duration-1000

缓动:ease-linear ease-in ease-out ease-in-out

延迟:delay-75 delay-100 delay-150 delay-200 delay-300 delay-500 delay-700 delay-1000

直接使用 Reanimated

仍可与 Uniwind 类一起使用:

import Animated, { FadeIn, FlipInXUp, LinearTransition } from 'react-native-reanimated';

<Animated.Text entering={FadeIn.delay(500)} className="text-foreground text-lg">
  Fading in
</Animated.Text>

<Animated.FlatList
  data={data}
  className="flex-none"
  contentContainerClassName="px-2"
  layout={LinearTransition}
  renderItem={({ item }) => (
    <Animated.Text entering={FlipInXUp} className="text-foreground py-2">
      {item}
    </Animated.Text>
  )}
/>
影子树更新

无需更改代码 —— props 直接连接到 C++ 引擎,自动消除重复渲染。

影子树诊断

仅供开发使用的 API,用于观察 C++ 引擎的行为 —— 组件何时向影子树注册/注销,以及样式如何流经它。从 uniwind/diagnostics 导入:

import { enableDiagnostics } from 'uniwind/diagnostics';

enableDiagnostics({
  reportMounts: true,    // log when components register with the shadow tree
  reportUnmounts: true,  // log when components unregister
  reportUpdates: true,   // log style updates with property-level detail
});

三个选项默认均为 false —— 只启用你需要的(在复杂界面上记录所有内容会变得很嘈杂)。更新日志分为 🔥 C++ 更新(通过影子树应用)和 ✨ 原生更新(直接设置在原生视图上的 props)—— 两者都零 React 重复渲染。

用于:调试主题切换(reportUpdates)、检测内存泄漏(离开时挂载/卸载计数应归零),以及验证零重复渲染。

平台支持:iOS 和 Android 上提供完整诊断。在 Web 上,enableDiagnostics 是一个空操作存根,不产生任何输出。

Group 变体

Tailwind group 变体通过 C++ 影子树将父级交互状态传播到后代。无重复渲染,无 context provider。

// Basic group — descendants react to parent press
<Pressable className="group p-4 bg-base rounded-xl">
  <Text className="text-default group-active:text-primary">Press the card</Text>
  <View className="size-8 bg-blue-500 rounded group-active:bg-red-500" />
</Pressable>

// Named groups — descendants pick which ancestor to follow
<Pressable className="group/card p-4 bg-base rounded-xl">
  <Pressable className="group/button px-3 py-1 bg-primary rounded">
    <Text className="text-white group-active/card:opacity-50 group-active/button:font-bold">
      Nested groups
    </Text>
  </Pressable>
</Pressable>

支持的变体:group-active:*(按下)、group-focus:*(聚焦)。命名变体:group-active/{name}:*、group-focus/{name}:*。

支持的 group 父级:Pressable(按下 + 聚焦)、Text(按下 —— 需要 onPress,即使为空)。TouchableOpacity、TouchableHighlight、TouchableWithoutFeedback 和 TextInput 不会充当 group 父级 —— 请将它们包裹在标记为 group 的 Pressable 中。

不支持:group-hover:*(原生上没有指针悬停)、group-disabled:*(会被解析但没有影子树触发)、任意 group-[.selector]:* 变体、隐式 in-* 变体。

默认样式(Pro 1.2.0+,实验性)

默认样式让 Pro 用户可以直接在 CSS 中为内置的 React Native 组件定义基础样式。它们默认禁用,需要实验性标志。

// metro.config.js
module.exports = withUniwindConfig(config, {
  cssEntryFile: './global.css',
  experimental: {
    defaultStyles: true,
  },
});
/* global.css */
View {
  border-color: var(--color-primary);
}

Text {
  font-family: Inter;
  font-size: 16px;
}

效果:每个 View 获得 border-color: var(--color-primary),每个 Text 获得 font-family: Inter 和 font-size: 16px,除非更具体的样式覆盖它们。

规则:

  • 仅在 Uniwind Pro 1.2.0+ 中可用
  • 默认禁用;需启用 experimental.defaultStyles: true
  • 实验性;可能不适用于所有用例,并可能在未来版本中变更
  • 使用 React Native 组件名作为选择器,而非 HTML 标签
  • 视为基础样式;直接的 className 样式可以覆盖它们

支持的组件选择器:ActivityIndicator、FlatList、Image、ImageBackground、InputAccessoryView、KeyboardAvoidingView、Modal、Pressable、RefreshControl、SafeAreaView、ScrollView、SectionList、Switch、Text、TextInput、TouchableHighlight、TouchableNativeFeedback、TouchableOpacity、TouchableWithoutFeedback、View、VirtualizedList。

Suspense 支持

React Suspense 边界内的组件会被正确处理。当某个子树被挂起时,Uniwind 会保持 C++ 影子条目存活,以便主题更新和运行时变化(深色模式、屏幕方向等)仍能到达挂起的节点。当树取消挂起时,样式已经是最新的 —— 不会出现闪烁的过时主题。

原生 Insets

移除 SafeAreaListener 设置 —— insets 由原生层注入:

// With Pro — just use safe area classes directly
<View className="pt-safe pb-safe">{/* content */}</View>
主题过渡(Pro)

切换主题时的原生动画过渡。支持 iOS、Android 和 Web。

import { Uniwind, ThemeTransitionPreset } from 'uniwind';

// Fade transition
Uniwind.setTheme('dark', { preset: ThemeTransitionPreset.Fade });

// Slide transitions
Uniwind.setTheme('dark', { preset: ThemeTransitionPreset.SlideRightToLeft });
Uniwind.setTheme('light', { preset: ThemeTransitionPreset.SlideLeftToRight });

// Circle mask transitions (expand from a corner or center)
Uniwind.setTheme('ocean', { preset: ThemeTransitionPreset.CircleCenter });

// Blur transitions
Uniwind.setTheme('dark', { preset: ThemeTransitionPreset.Blur });
Uniwind.setTheme('dark', { preset: ThemeTransitionPreset.BlurRightToLeft });

// No animation
Uniwind.setTheme('light');

可用预设:

预设效果
ThemeTransitionPreset.None即时切换,无动画
ThemeTransitionPreset.Fade主题之间交叉淡入淡出
ThemeTransitionPreset.SlideRightToLeft新主题从右侧滑入
ThemeTransitionPreset.SlideLeftToRight新主题从左侧滑入
ThemeTransitionPreset.CircleTopRight圆形遮罩从右上角扩展
ThemeTransitionPreset.CircleTopLeft圆形遮罩从左上角扩展
ThemeTransitionPreset.CircleBottomRight圆形遮罩从右下角扩展
ThemeTransitionPreset.CircleBottomLeft圆形遮罩从左下角扩展
ThemeTransitionPreset.CircleCenter圆形遮罩从中心扩展
ThemeTransitionPreset.Blur模糊淡出动画
ThemeTransitionPreset.BlurRightToLeft从右到左的方向性模糊
ThemeTransitionPreset.BlurLeftToRight从左到右的方向性模糊

设置诊断

当样式不生效时,按以下顺序检查:

1. package.json
  • dependencies 中包含 "uniwind"(或 "uniwind-pro")
  • "tailwindcss" 为 v4+(^4.0.0)
  • 对于 Pro:react-native-nitro-modules、react-native-reanimated、react-native-worklets
2. metro.config.js
  • withUniwindConfig 从 'uniwind/metro' 导入
  • withUniwindConfig 是最外层包装
  • cssEntryFile 是相对路径字符串(例如 './global.css')
  • 不要使用 path.resolve() 或绝对路径
  • 对于 Pro 默认样式:已设置 experimental.defaultStyles: true
3. global.css
  • 包含 @import 'tailwindcss'; 和 @import 'uniwind';
  • 在 App.tsx 或根布局中导入,不要在 index.ts/index.js 中导入
  • 其位置决定了 Tailwind 扫描的应用根目录
4. babel.config.js(仅 Pro)
  • plugins 数组中包含 'react-native-worklets/plugin'
5. TypeScript
  • uniwind-types.d.ts 存在(运行 Metro 后生成)
  • 已包含在 tsconfig.json 中,或放置在 src//app/ 目录中
6. 构建
  • 配置更改后已重启 Metro 服务器
  • 已清除 Metro 缓存(npx expo start --clear 或 npx react-native start --reset-cache)
  • 已完成原生重新构建(如果是 Pro 或依赖项更改后)

故障排除

现象原因修复方法
样式未生效global.css 中缺少导入添加 @import 'tailwindcss'; @import 'uniwind';
样式未生效global.css 被导入到了 index.js 中将导入移到 App.tsx 或 _layout.tsx
未检测到类名global.css 位于嵌套目录,组件位于其他位置在 global.css 中添加 @source '../components'
className 上出现 TypeScript 错误缺少类型文件运行 Metro 以生成 uniwind-types.d.ts
withUniwindConfig is not a function导入错误使用 require('uniwind/metro'),而不是 require('uniwind')
热重载触发整页重载global.css 被导入到了错误的文件中将其移到 App.tsx 或根布局
cssEntryFile 错误 / Metro 崩溃使用了绝对路径使用相对路径:'./global.css'
withUniwindConfig 不在最外层另一个 wrapper 包裹了 Uniwind交换顺序,使 Uniwind 位于最外层
深色主题不生效缺少 @variant dark在 @layer theme 中定义 dark variant
自定义主题未出现未在 metro 配置中注册添加到 extraThemes 数组,重启 Metro
字体未加载字体名称不匹配CSS 字体名称必须与文件名完全一致(不含扩展名)
rem 值过大/过小基础 rem 设置错误为兼容 NativeWind 设置 polyfills: { rem: 14 }
不支持的 CSS 警告使用了 Web 专用的 CSS启用 debug: true 以定位;移除不支持的属性
Failed to serialize javascript object复杂 CSS、循环引用或缓存过期清除缓存:watchman watch-del-all; rm -rf node_modules/.cache; npx expo start --clear。同时检查扫描路径中是否包含含有 CSS 类名的文档/markdown 文件(见下文)
来自 llms-full.txt 或文档的 Failed to serialize javascript object项目目录中含有 CSS 类名的文档/markdown 文件被 Tailwind 扫描到将含有 CSS 示例的 .md 文件移出项目根目录,或添加到 .gitignore 让 Tailwind 的扫描器跳过它们
unstable_enablePackageExports 冲突应用禁用了 package exports为 Uniwind 和 culori 使用选择性 resolver
来自 monorepo 包的类名缺失未包含在 Tailwind 扫描中在 global.css 中添加 @source '../../packages/ui'
生产中来自 node_modules 库的类名缺失(bun)Bun 使用符号链接;Tailwind 的 Oxide 扫描器无法跟随它们使用解析后的路径:@source "../../node_modules/heroui-native/lib",并在 .npmrc 中添加 public-hoist-pattern[]=heroui-native
active: 与 withUniwind 搭配不生效withUniwind 不支持交互状态选择器只有核心 RN 的 Pressable/TextInput/Switch 支持 active:/focus:/disabled:。使用 withUniwind 包裹的第三方 pressable 无法获得状态
withUniwind 自定义映射会覆盖 className+style 合并提供手动映射时,style prop 不会被合并使用自动映射(不传第二个参数)来实现 className+style 合并。对于手动映射 + className,需双重包裹:withUniwind(withUniwind(Comp), { mapping })
withUniwind 在 ref 上丢失泛型类型(例如 FlashList<T>)TypeScript 对 HOC 的限制手动转换 ref:ref={scrollRef as any}
平台特定字体:@theme 块报错在 @theme {} 内使用 @media ios/android改用 @layer theme { :root { @variant ios { ... } } } —— @theme 只接受自定义属性,平台选择应使用 @variant 而不是 @media
Uniwind.setTheme('system') 在 Android 上崩溃(RN 0.82+)RN 0.82 更改了 Appearance API更新到最新版 Uniwind(已修复)。在旧版 Uniwind + RN 0.82+ 上避免使用 setTheme('system')
初始加载时样式闪烁/消失(Android)SafeAreaListener 在组件监听器挂载前触发已在近期版本中修复。若仍存在,请确保 Uniwind 为最新版
useTVEventHandler 为 undefinedUniwind 的模块替换干扰了 tvOS 导出已在 v1.2.1+ 中修复。请更新 Uniwind
@layer theme 变量在 Web 上未渲染RNW + Expo SDK 55 的 bug已在 v1.4.1+ 中修复。请更新 Uniwind
应用启动时 updateCSSVariables 主题错误连续为多个主题调用;首次渲染时最后一次调用生效将当前主题的 updateCSSVariables 放在最后调用。初始加载后,顺序不再重要
Pro:动画不生效缺少 Babel 插件将 react-native-worklets/plugin 添加到 babel.config.js
Pro:模块未找到未重新构建原生代码运行 npx expo prebuild --clean,然后运行 npx expo run:ios
Pro:postinstall 失败包管理器阻止脚本执行添加到 trustedDependencies(bun)或配置 yarn/pnpm
Pro:认证过期登录会话过期(有效期 180 天)运行 npx uniwind-pro,重新登录
Pro:达到下载上限已达到每月下载上限查看 Pro 仪表盘,上限每月重置
Pro:Uniwind.updateInsets 被不必要地调用Pro 会原生注入 insets在 Pro 中 Uniwind.updateInsets 是空操作。使用 Pro 时移除 SafeAreaListener 设置
Pro:主题过渡崩溃缺少 ThemeTransitionPreset 导入,或在应用就绪前调用从 'uniwind' 导入。确保应用完全挂载后再带过渡调用 setTheme
Pro:默认组件样式未生效功能被禁用或选择器不受支持使用 Uniwind Pro 1.2.0+,启用 experimental.defaultStyles: true,重启 Metro,并使用受支持的 RN 组件选择器,如 View 或 Text
unstable_enablePackageExports 选择性解析器

如果你的应用禁用了 unstable_enablePackageExports(在加密应用中很常见),请使用选择性解析器:

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);
};

FAQ

在 Expo Router 中应该把 global.css 放在哪里? 项目根目录。在 app/_layout.tsx 中导入。如果放在 app/ 中,请为同级目录添加 @source。

Uniwind 能在 Expo Go 中使用吗? 免费版:可以。Pro 版:不可以——需要原生重新构建(development builds)。

我可以使用 tailwind.config.js 吗? 不可以。Uniwind 使用 Tailwind v4——所有配置都通过 global.css 中的 @theme 完成。

如何在 JS 中访问 CSS 变量? 在组件内部:useCSSVariable('--color-primary')(响应式)。在 React 之外:Uniwind.getCSSVariable('--color-primary')(一次性,1.6.4+)。对于未在 classNames 中使用的变量,请用 @theme static 定义。

我可以使用 Platform.select() 吗? 可以,但更推荐平台选择器(ios:pt-12 android:pt-6)——更简洁,无需导入。

是否支持 Next.js? 未官方支持。社区插件:uniwind-plugin-next。对于 Next.js,请使用标准 Tailwind CSS。

是否支持 Vite? 支持,自 v1.2.0 起。请将 uniwind/vite 插件与 @tailwindcss/vite 一起使用。

CSS 变更时整个应用会重新加载? Metro 无法热重载包含大量 provider 的文件。请将 global.css 的导入移到组件树中更深的位置。

样式优先级? 重要工具类(如 bg-red-500!)会覆盖同一属性的非重要工具类,并可与变体一起使用(active:bg-red-500!、ios:pt-12!)。内联 style 始终覆盖 className,即使是重要工具类。静态样式请使用 className,仅对真正动态的值使用内联。对于 classNames 可能冲突的组件库,请使用 tailwind-merge 的 cn()。

如何包含自定义字体? 加载字体文件(Expo:在 app.json 中使用 expo-font 插件;Bare RN:react-native-asset),然后在 CSS 中映射:@theme { --font-sans: 'Roboto-Regular'; }。字体名称必须与文件名完全一致。参见上文的 Fonts 部分。

如何根据 prop 值设置样式? 使用数据选择器:data-[selected=true]:ring-2。仅支持相等性检查。参见上文的 Data Selectors 部分。

如何使用渐变? 内置:bg-gradient-to-r from-red-500 to-green-500。还支持基于角度(bg-linear-90)和任意值(bg-linear-[45deg,#f00_0%,#00f_100%])。参见上文的 Gradients 部分。

className 去重是如何工作的? Uniwind 不会自动对冲突的 classNames 去重。请使用 tailwind-merge 搭配 cn() 工具函数。参见上文的 cn Utility 部分。

如何调试 'Failed to serialize javascript object'? 清除缓存:watchman watch-del-all; rm -rf node_modules/.cache; npx expo start --clear。在 metro 配置中启用 debug: true 以定位有问题的 CSS 模式。参见上文的 Troubleshooting 表格。

如何启用安全区域 classNames? 免费版:安装 react-native-safe-area-context,用 SafeAreaListener 包裹根组件,调用 Uniwind.updateInsets(insets)。Pro 版:自动完成——无需设置。然后使用 pt-safe、pb-safe 等。参见上文的 Safe Area Utilities 部分。

哪些 UI 套件与 Uniwind 配合良好? React Native Reusables(shadcn 理念,复制粘贴式组件)和 HeroUI Native(完整库,针对 Uniwind 优化)。任何库都可以通过 withUniwind 包装器使用。参见上文的 UI Kit Compatibility 部分。

我可以将主题限定到单个组件吗? 可以,使用 ScopedTheme:<ScopedTheme theme="dark"><Card /></ScopedTheme>。它会为子树强制应用某个主题,而不改变全局主题。参见 Theming 部分。

active: 能与 react-native-gesture-handler 的 Pressable 一起使用吗? 不可以。withUniwind 不支持交互状态选择器(active:、focus:、disabled:)。只有核心 RN 的 Pressable、TextInput 和 Switch 支持它们。对于 RNGH 组件,请使用 onPressIn/onPressOut 配合状态。 我可以自定义默认的 border 颜色吗? 可以 —— 使用 @utility border 完全覆盖该类:

@utility border {
  border-width: 1px;
  border-style: solid;
  border-color: var(--color-primary);
}

这会完全替换内置的 border 行为,因此请重新声明你仍然需要的所有属性。或者,显式使用 border border-gray-300,或在 @theme 中定义 --color-border 并使用 border-border。

我可以在 @theme {} 中使用平台特定字体吗? 不可以。@theme {} 只接受自定义属性。请改用 @layer theme { :root { @variant ios { --font-sans: '...'; } } }。注意:在 CSS 中进行平台选择时请使用 @variant(而不是 @media)。

Uniwind 是否支持 ESM metro 配置(metro.config.mjs 或 metro.config.ts)? 自 v1.3.0+ 起支持。如果你遇到 Class extends value is not a constructor,请更新 Uniwind 或使用 CommonJS 的 metro.config.js。

来自 node_modules 的类在开发环境有效,但在生产环境无效(bun)? Bun 会为包创建符号链接。Tailwind 的 Oxide 扫描器无法跟随它们。请使用解析后的 @source 路径并提升该包。详情请参阅 UI Kit 兼容性。

调试时如何清除所有缓存? watchman watch-del-all 2>/dev/null; rm -rf node_modules/.cache && npx expo start --clear。这会清除 Watchman、Babel/打包器缓存以及 Expo 内部缓存。

MCP 服务

Uniwind 提供用于 AI 辅助开发的官方 MCP 服务。如果用户的环境支持 MCP,建议进行设置:https://docs.uniwind.dev/mcp

相关技能

NativeWind → Uniwind 迁移:使用 migrate-nativewind-to-uniwind 技能从 NativeWind 迁移。它会处理包移除、配置迁移、Tailwind v4 升级、cssInterop 移除、主题转换以及所有破坏性变更。它已经过测试,可用于生产环境。

重要:不要猜测 Uniwind API。如果你对任何 Uniwind API、Hook、组件或配置选项不确定,请查阅并对照官方文档进行核实:https://docs.uniwind.dev/llms-full.txt

评论

支持 Markdown