uniwind
将Tailwind CSS v4样式应用于React Native项目,实现跨平台统一设计,提升开发效率。
npx skills add uni-stack/uniwind --skill uniwindBefore / After 效果对比
1 组在React Native中实现复杂的UI样式,通常需要编写大量CSS-in-JS或StyleSheet对象,导致样式代码冗长、难以维护,且缺乏统一的设计系统,开发效率受限。
Uniwind将Tailwind CSS v4的强大功能带入React Native,让开发者能够以原子化的方式快速构建美观的UI。它极大地简化了样式编写,提升了开发速度,并确保了跨平台的视觉一致性。
Uniwind — Complete Reference
Uniwind 1.5.0+ / Tailwind CSS v4 / React Native 0.81+ / Expo SDK 54+
If user has lower version, recommend updating to 1.5.0+ for best experience.
Uniwind brings Tailwind CSS v4 to React Native. All core React Native components support the className prop out of the box. Styles are compiled at build time — no runtime overhead.
Critical Rules
- Tailwind v4 only — Use
@import 'tailwindcss'not@tailwind base. Tailwind v3 is not supported. - Never construct classNames dynamically — Tailwind scans at build time.
bg-${color}-500will NOT work. Use complete string literals, mapping objects, or ternaries. - Never use
cssInteroporremapProps— Those are NativeWind APIs. Uniwind does not override global components. - No
tailwind.config.js— All config goes inglobal.cssvia@themeand@layer theme. - No ThemeProvider required — Use
Uniwind.setTheme()directly. withUniwindConfigmust be the outermost Metro config wrapper.- NEVER wrap
react-nativeorreact-native-reanimatedcomponents withwithUniwind—View,Text,Pressable,Image,TextInput,ScrollView,FlatList,Switch,Modal,Animated.View,Animated.Text, etc. already have fullclassNamesupport built in. Wrapping them withwithUniwindwill break behavior. Only usewithUniwindfor third-party components (e.g.,expo-image,expo-blur,moti). - Font families: single font only — React Native doesn't support fallbacks. Use
--font-sans: 'Roboto-Regular'not'Roboto', sans-serif. - All theme variants must define the same set of CSS variables — If
lightdefines--color-primary, thendarkand every custom theme must too. Mismatched variables cause runtime errors. accent-prefix is REQUIRED for non-style color props — This is crucial. Props likecolor(Button, ActivityIndicator),tintColor(Image),thumbColor(Switch),placeholderTextColor(TextInput) are NOT part of thestyleobject. You MUST use the corresponding{propName}ClassNameprop withaccent-prefixed classes. Example:<ActivityIndicator colorClassName="accent-blue-500" />NOT<ActivityIndicator className="text-blue-500" />. Regular Tailwind color classes (liketext-blue-500) only work onclassName(which maps tostyle). For non-style color props, always useaccent-.- rem default is 16px — NativeWind used 14px. Set
polyfills: { rem: 14 }in metro config if migrating. cssEntryFilemust be a relative path string — Use'./global.css'notpath.resolve(__dirname, 'global.css').- Deduplicate with
cn()when mixing custom CSS classes and Tailwind — Uniwind does NOT auto-deduplicate. If a custom CSS class (.card { padding: 16px }) and a Tailwind utility (p-6) set the same property, both apply with unpredictable results. Always wrap withcn('card', 'p-6')when there's overlap.
Setup
Installation
# or other package manager
bun install uniwind tailwindcss
Requires Tailwind CSS v4+.
global.css
Create a CSS entry file:
@import 'tailwindcss';
@import 'uniwind';
Import in your App component (e.g., App.tsx or app/_layout.tsx), NOT in index.ts/index.js — importing there breaks hot reload:
// app/_layout.tsx or App.tsx
import './global.css';
The directory containing global.css is the app root — Tailwind scans for classNames starting from this directory.
Metro Configuration
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
});
For most flows, keep defaults, only provide cssEntryFile.
Wrapper order — Uniwind must wrap everything else:
// 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 Configuration (v1.2.0+)
If user has storybook setup, add extra vite config:
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 auto-generates a .d.ts file (default: ./uniwind-types.d.ts) after running Metro. Place it in src/ or app/ for auto-inclusion, or add to tsconfig.json:
{ "include": ["./uniwind-types.d.ts"] }
If user has some typescript errors related to classNames, just run metro server to build the d.ts file.
Expo Router Placement
project/
├── app/_layout.tsx ← import '../global.css' here
├── components/
├── global.css ← project root (best location)
└── metro.config.js ← cssEntryFile: './global.css'
If global.css is in app/ dir, add @source for sibling directories:
@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 Support
Add @source directives in global.css for packages outside the CSS entry file's directory:
@import 'tailwindcss';
@import 'uniwind';
@source "../../packages/ui/src";
@source "../../packages/shared/src";
Also needed for node_modules packages that contain Uniwind classes (e.g., shared UI libraries).
Component Bindings
All core React Native components support className out of the box. Some have additional className props for sub-styles (like contentContainerClassName) and non-style color props (requiring accent- prefix).
Complete Reference
Legend: Props marked with ⚡ require the accent- prefix. Props in parentheses are platform-specific.
View
| Prop | Maps to | Prefix |
|---|---|---|
className | style | — |
Text
| Prop | Maps to | Prefix |
|---|---|---|
className | style | — |
selectionColorClassName | selectionColor | ⚡ accent- |
Pressable
| Prop | Maps to | Prefix |
|---|---|---|
className | style | — |
Supports active:, disabled:, focus: state selectors.
Image
| Prop | Maps to | Prefix |
|---|---|---|
className | style | — |
tintColorClassName | tintColor | ⚡ accent- |
TextInput
| Prop | Maps to | Prefix |
|---|---|---|
className | style | — |
cursorColorClassName | cursorColor | ⚡ accent- |
selectionColorClassName | selectionColor | ⚡ accent- |
placeholderTextColorClassName | placeholderTextColor | ⚡ accent- |
selectionHandleColorClassName | selectionHandleColor | ⚡ accent- |
underlineColorAndroidClassName | underlineColorAndroid (Android) | ⚡ accent- |
Supports focus:, active:, disabled: state selectors.
ScrollView
| Prop | Maps to | Prefix |
|---|---|---|
className | style | — |
contentContainerClassName | contentContainerStyle | — |
endFillColorClassName | endFillColor | ⚡ accent- |
FlatList
| Prop | Maps to | Prefix |
|---|---|---|
className | style | — |
contentContainerClassName | contentContainerStyle | — |
columnWrapperClassName | columnWrapperStyle | — |
ListHeaderComponentClassName | ListHeaderComponentStyle | — |
ListFooterComponentClassName | ListFooterComponentStyle | — |
endFillColorClassName | endFillColor | ⚡ accent- |
SectionList
| Prop | Maps to | Prefix |
|---|---|---|
className | style | — |
contentContainerClassName | contentContainerStyle | — |
ListHeaderComponentClassName | ListHeaderComponentStyle | — |
ListFooterComponentClassName | ListFooterComponentStyle | — |
endFillColorClassName | endFillColor | ⚡ accent- |
VirtualizedList
| Prop | Maps to | Prefix |
|---|---|---|
className | style | — |
contentContainerClassName | contentContainerStyle | — |
ListHeaderComponentClassName | ListHeaderComponentStyle | — |
ListFooterComponentClassName | ListFooterComponentStyle | — |
endFillColorClassName | endFillColor | ⚡ accent- |
Switch
| Prop | Maps to | Prefix |
|---|---|---|
thumbColorClassName | thumbColor | ⚡ accent- |
trackColorOnClassName | trackColor.true (on) | ⚡ accent- |
trackColorOffClassName | trackColor.false (off) | ⚡ accent- |
ios_backgroundColorClassName | ios_backgroundColor (iOS) | ⚡ accent- |
Note: Switch does NOT support className (className?: never in types). Use only the color-specific className props above. Supports disabled: state selector.
ActivityIndicator
| Prop | Maps to | Prefix |
|---|---|---|
className | style | — |
colorClassName | color | ⚡ accent- |
Button
| Prop | Maps to | Prefix |
|---|---|---|
colorClassName | color | ⚡ accent- |
Note: Button does not support className (no style prop on RN Button).
Modal
| Prop | Maps to | Prefix |
|---|---|---|
className | style | — |
backdropColorClassName | backdropColor | ⚡ accent- |
RefreshControl
| Prop | Maps to | Prefix |
|---|---|---|
className | style | — |
colorsClassName | colors (Android) | ⚡ accent- |
tintColorClassName | tintColor (iOS) | ⚡ accent- |
titleColorClassName | titleColor (iOS) | ⚡ accent- |
progressBackgroundColorClassName | progressBackgroundColor (Android) | ⚡ accent- |
ImageBackground
| Prop | Maps to | Prefix |
|---|---|---|
className | style | — |
imageClassName | imageStyle | — |
tintColorClassName | tintColor | ⚡ accent- |
SafeAreaView
| Prop | Maps to | Prefix |
|---|---|---|
className | style | — |
KeyboardAvoidingView
| Prop | Maps to | Prefix |
|---|---|---|
className | style | — |
contentContainerClassName | contentContainerStyle | — |
InputAccessoryView
| Prop | Maps to | Prefix |
|---|---|---|
className | style | — |
backgroundColorClassName | backgroundColor | ⚡ accent- |
TouchableHighlight
| Prop | Maps to | Prefix |
|---|---|---|
className | style | — |
underlayColorClassName | underlayColor | ⚡ accent- |
Supports active:, disabled: state selectors.
TouchableOpacity
| Prop | Maps to | Prefix |
|---|---|---|
className | style | — |
Supports active:, disabled: state selectors.
TouchableNativeFeedback
| Prop | Maps to | Prefix |
|---|---|---|
className | style | — |
Supports active:, disabled: state selectors.
TouchableWithoutFeedback
| Prop | Maps to | Prefix |
|---|---|---|
className | style | — |
Supports active:, disabled: state selectors.
Usage Examples
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
...
用户评价 (0)
发表评价
暂无评价
统计数据
用户评分
为此 Skill 评分