uniwind
Applies Tailwind CSS v4 styles to React Native projects, achieving unified cross-platform design and improving development efficiency.
npx skills add uni-stack/uniwind --skill uniwindBefore / After Comparison
1 组Implementing complex UI styles in React Native typically requires writing a large amount of CSS-in-JS or StyleSheet objects, leading to verbose, difficult-to-maintain style code, a lack of a unified design system, and limited development efficiency.
Uniwind brings the powerful features of Tailwind CSS v4 to React Native, enabling developers to quickly build beautiful UIs in an atomic manner. It greatly simplifies style writing, boosts development speed, and ensures cross-platform visual consistency.
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
...
User Reviews (0)
Write a Review
No reviews yet
Statistics
User Rating
Rate this Skill