--- name: uniwind description: > Uniwind — Tailwind CSS v4 styling for React Native. Use when adding, building, or styling components in a React Native project that uses Tailwind with className. --- # Uniwind — Complete Reference > Uniwind 1.6.0+ / Tailwind CSS v4 / React Native 0.81+ / Expo SDK 54+ If user has lower version, recommend updating to 1.6.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 1. **Tailwind v4 only** — Use `@import 'tailwindcss'` not `@tailwind base`. Tailwind v3 is not supported. 2. **Never construct classNames dynamically** — Tailwind scans at build time. `bg-${color}-500` will NOT work. Use complete string literals, mapping objects, or ternaries. 3. **Never use `cssInterop` or `remapProps`** — Those are NativeWind APIs. Uniwind does not override global components. 4. **No `tailwind.config.js`** — All config goes in `global.css` via `@theme` and `@layer theme`. 5. **No ThemeProvider required** — Use `Uniwind.setTheme()` directly. 6. **`withUniwindConfig` must be the outermost** Metro config wrapper. 7. **NEVER wrap `react-native` or `react-native-reanimated` components with `withUniwind`** — `View`, `Text`, `Pressable`, `Image`, `TextInput`, `ScrollView`, `FlatList`, `Switch`, `Modal`, `Animated.View`, `Animated.Text`, etc. already have full `className` support built in. Wrapping them with `withUniwind` will break behavior. Only use `withUniwind` for **third-party** components (e.g., `expo-image`, `expo-blur`, `moti`). 8. **Font families: single font only** — React Native doesn't support fallbacks. Use `--font-sans: 'Roboto-Regular'` not `'Roboto', sans-serif`. 9. **All theme variants must define the same set of CSS variables** — If `light` defines `--color-primary`, then `dark` and every custom theme must too. Mismatched variables cause runtime errors. 10. **`accent-` prefix is REQUIRED for non-style color props** — This is crucial. Props like `color` (Button, ActivityIndicator), `tintColor` (Image), `thumbColor` (Switch), `placeholderTextColor` (TextInput) are NOT part of the `style` object. You MUST use the corresponding `{propName}ClassName` prop with `accent-` prefixed classes. Example: `` NOT ``. Regular Tailwind color classes (like `text-blue-500`) only work on `className` (which maps to `style`). For non-style color props, always use `accent-`. 11. **rem default is 16px** — NativeWind used 14px. Set `polyfills: { rem: 14 }` in metro config if migrating. 12. **`cssEntryFile` must be a relative path string** — Use `'./global.css'` not `path.resolve(__dirname, 'global.css')`. 13. **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 with `cn('card', 'p-6')` when there's overlap. 14. **Important utilities are supported** — Tailwind important modifier works in classNames with `!` at the end: `bg-red-500!`, `active:bg-red-500!`, `ios:pt-12!`. Leading `!bg-red-500` syntax is deprecated. Important utilities override non-important utilities for the same style property, but inline `style` still overrides className. ## Setup ### Installation ```bash # or other package manager bun install uniwind tailwindcss ``` Requires **Tailwind CSS v4+**. ### global.css Create a CSS entry file: ```css @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: ```tsx // 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 ```js 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: ```js // 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: ```ts 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`: ```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 ```text 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: ```css @import 'tailwindcss'; @import 'uniwind'; @source '../components'; ``` ### Tailwind IntelliSense (VS Code / Cursor / Windsurf) ```json { "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: ```css @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 ```tsx import { View, Text, Pressable, TextInput, ScrollView, FlatList, Switch, Image, ActivityIndicator, Modal, RefreshControl, Button } from 'react-native'; // View — basic layout Title // Pressable — with press/focus states Press Me // TextInput — with focus state and accent- color props // ScrollView — with content container {/* content */} // FlatList — with all sub-style props } /> // Switch — no className support, use color-specific props only // Image — tint color // ActivityIndicator // Button — only colorClassName (no className)