# Uniwind
**Version 0.1.0**
Uniwind
February 2026
> **Note:**
> This document is mainly for agents and LLMs to follow when maintaining,
> generating, or refactoring codebases. Humans may also find it useful,
> but guidance here is optimized for automation and consistency by AI-assisted workflows.
---
## Abstract
Comprehensive best practices guide for Uniwind, the fastest Tailwind CSS bindings for React Native. Contains 45+ rules across 8 categories, prioritized by impact from critical (build-time configuration, theme architecture) to incremental (migration compatibility). Each rule includes detailed explanations, real-world examples comparing incorrect vs. correct implementations, and specific impact metrics to guide automated refactoring and code generation.
---
## Table of Contents
1. [Build-Time Configuration](#1-build-time-configuration) — **CRITICAL**
- 1.1 [Configure Metro Plugin with Required Options](#11-configure-metro-plugin-with-required-options)
- 1.2 [Configure rem Base Value for Design System Consistency](#12-configure-rem-base-value-for-design-system-consistency)
- 1.3 [Configure TypeScript Definition File Location](#13-configure-typescript-definition-file-location)
- 1.4 [Enable Debug Mode During Development](#14-enable-debug-mode-during-development)
- 1.5 [Place CSS Entry File in App Root Directory](#15-place-css-entry-file-in-app-root-directory)
- 1.6 [Restart Metro After Configuration Changes](#16-restart-metro-after-configuration-changes)
2. [Theme Architecture](#2-theme-architecture) — **CRITICAL**
- 2.1 [Define Identical Variables Across All Themes](#21-define-identical-variables-across-all-themes)
- 2.2 [Define Theme Variables with @theme Directive](#22-define-theme-variables-with-theme-directive)
- 2.3 [Register Custom Themes in Metro Config](#23-register-custom-themes-in-metro-config)
- 2.4 [Remove ThemeProvider Wrapper from App](#24-remove-themeprovider-wrapper-from-app)
- 2.5 [Use @variant Blocks for Theme Definitions](#25-use-variant-blocks-for-theme-definitions)
- 2.6 [Use light-dark() Function for Adaptive Colors](#26-use-light-dark-function-for-adaptive-colors)
- 2.7 [Use OKLCH Color Space for Perceptual Uniformity](#27-use-oklch-color-space-for-perceptual-uniformity)
3. [Component Integration](#3-component-integration) — **HIGH**
- 3.1 [Define Wrapped Components at Module Level](#31-define-wrapped-components-at-module-level)
- 3.2 [Reanimated Components Work Without withUniwind](#32-reanimated-components-work-without-withuniwind)
- 3.3 [Use accent-* Classes for Color Prop Extraction](#33-use-accent-classes-for-color-prop-extraction)
- 3.4 [Use Custom CSS Classes for Complex Reusable Styles](#34-use-custom-css-classes-for-complex-reusable-styles)
- 3.5 [Use Custom Prop Mappings for Non-Style Props](#35-use-custom-prop-mappings-for-non-style-props)
- 3.6 [Use withUniwind for Third-Party Components](#36-use-withuniwind-for-third-party-components)
4. [Responsive Design](#4-responsive-design) — **HIGH**
- 4.1 [Define Custom Breakpoints with Semantic Names](#41-define-custom-breakpoints-with-semantic-names)
- 4.2 [Design Mobile-First with Progressive Enhancement](#42-design-mobile-first-with-progressive-enhancement)
- 4.3 [Limit Breakpoints to 3-5 for Maintainability](#43-limit-breakpoints-to-3-5-for-maintainability)
- 4.4 [Scale Spacing and Typography Responsively](#44-scale-spacing-and-typography-responsively)
- 4.5 [Use hidden/flex for Responsive Visibility](#45-use-hiddenflex-for-responsive-visibility)
5. [Performance Optimization](#5-performance-optimization) — **MEDIUM-HIGH**
- 5.1 [Combine className and style Prop Correctly](#51-combine-classname-and-style-prop-correctly)
- 5.2 [Memoize Variant Style Objects](#52-memoize-variant-style-objects)
- 5.3 [Prefer className Over Inline style Prop](#53-prefer-classname-over-inline-style-prop)
- 5.4 [Use Complete Static Class Names for Build-Time Resolution](#54-use-complete-static-class-names-for-build-time-resolution)
- 5.5 [Use tailwind-merge for Class Deduplication](#55-use-tailwind-merge-for-class-deduplication)
- 5.6 [Use useResolveClassNames Sparingly](#56-use-useresolveclassnames-sparingly)
6. [Platform Patterns](#6-platform-patterns) — **MEDIUM**
- 6.1 [Configure Font Families Without Fallbacks](#61-configure-font-families-without-fallbacks)
- 6.2 [Understand Yoga Layout Engine Differences](#62-understand-yoga-layout-engine-differences)
- 6.3 [Use Platform Selectors for iOS/Android Differences](#63-use-platform-selectors-for-iosandroid-differences)
- 6.4 [Use react-native-safe-area-context for Safe Areas](#64-use-react-native-safe-area-context-for-safe-areas)
- 6.5 [Use web: Selector for Cross-Platform Apps](#65-use-web-selector-for-cross-platform-apps)
7. [State & Interaction](#7-state-interaction) — **MEDIUM**
- 7.1 [Avoid hover: on Native - Use active: Instead](#71-avoid-hover-on-native-use-active-instead)
- 7.2 [Use dark: Variant for Dark Mode Styles](#72-use-dark-variant-for-dark-mode-styles)
- 7.3 [Use Data Selectors for Component State Styling](#73-use-data-selectors-for-component-state-styling)
- 7.4 [Use Group Variants for Parent-Child Styling (WIP)](#74-use-group-variants-for-parent-child-styling-wip)
- 7.5 [Use Pressable with active:/focus:/disabled: States](#75-use-pressable-with-activefocusdisabled-states)
8. [Migration & Compatibility](#8-migration-compatibility) — **LOW-MEDIUM**
- 8.1 [Account for Different rem Default Values](#81-account-for-different-rem-default-values)
- 8.2 [Follow NativeWind Migration Checklist](#82-follow-nativewind-migration-checklist)
- 8.3 [Replace *-safe Classes with Safe Area Context](#83-replace-safe-classes-with-safe-area-context)
- 8.4 [Replace cssInterop with withUniwind](#84-replace-cssinterop-with-withuniwind)
- 8.5 [Use Tailwind 4 CSS-First Configuration](#85-use-tailwind-4-css-first-configuration)
---
## 1. Build-Time Configuration
**Impact: CRITICAL**
Metro plugin setup, CSS entry points, and type generation determine base performance ceiling. Misconfigurations cascade through the entire app, causing missing styles or build failures.
### 1.1 Configure Metro Plugin with Required Options
**Impact: CRITICAL (missing configuration causes zero styles to apply)**
The Metro plugin is required for Uniwind to function. Without proper configuration, no styles will be processed at build time.
**Incorrect (missing Uniwind configuration):**
```javascript
// metro.config.js
const { getDefaultConfig } = require('expo/metro-config')
module.exports = getDefaultConfig(__dirname)
// No Uniwind integration - styles won't work
```
**Correct (properly configured):**
```javascript
// metro.config.js
const { getDefaultConfig } = require('expo/metro-config')
const { withUniwindConfig } = require('uniwind/metro')
const config = getDefaultConfig(__dirname)
module.exports = withUniwindConfig(config, {
cssEntryFile: './src/global.css',
dtsFile: './src/uniwind-types.d.ts',
})
```
**Key configuration options:**
- `cssEntryFile` (required): Path to your CSS entry file
- `dtsFile` (optional): Path for generated TypeScript definitions
- `extraThemes` (optional): Array of custom theme names
- `debug` (optional): Enable debug mode for development
Reference: [Uniwind Metro Config](https://docs.uniwind.dev/api/metro-config)
### 1.2 Configure rem Base Value for Design System Consistency
**Impact: HIGH (mismatched rem values cause incorrect spacing across entire app)**
Uniwind defaults to 16px for rem calculations. If your design system uses a different base, configure the polyfill to match.
**Incorrect (assuming NativeWind's 14px default):**
```javascript
// metro.config.js
module.exports = withUniwindConfig(config, {
cssEntryFile: './src/global.css',
// Using default 16px, but design uses 14px base
})
```
```tsx
// p-4 = 16px (1rem × 16), but design expects 14px
```
**Correct (matching design system):**
```javascript
// metro.config.js
module.exports = withUniwindConfig(config, {
cssEntryFile: './src/global.css',
polyfills: {
rem: 14, // Match your design system's base
},
})
```
**When to change rem:**
- Migrating from NativeWind (uses 14px default)
- Design system specifies different base font size
- Web app migration with existing rem-based spacing
**Keep default 16px when:**
- Starting fresh with Uniwind
- Using Tailwind's standard spacing scale
- No existing design system constraints
Reference: [Uniwind Metro Config](https://docs.uniwind.dev/api/metro-config)
### 1.3 Configure TypeScript Definition File Location
**Impact: CRITICAL (enables autocomplete for all utilities and theme tokens)**
Uniwind generates TypeScript definitions during build. Proper placement enables autocomplete for all utilities, platform variants, and custom CSS classes.
**Incorrect (types in root, not included in tsconfig):**
```javascript
// metro.config.js
module.exports = withUniwindConfig(config, {
cssEntryFile: './src/global.css',
dtsFile: './uniwind-types.d.ts', // Root level, may not be included
})
```
**Correct (types in src for automatic inclusion):**
```javascript
// metro.config.js
module.exports = withUniwindConfig(config, {
cssEntryFile: './src/global.css',
dtsFile: './src/uniwind-types.d.ts', // Auto-included by TypeScript
})
```
**Alternative (explicit tsconfig inclusion):**
```json
// tsconfig.json
{
"compilerOptions": {
"types": ["./uniwind-types.d.ts"]
}
}
```
**Benefits of proper type generation:**
- Autocomplete for all Tailwind utilities
- Type checking for platform variants (`ios:`, `android:`)
- IntelliSense for custom CSS classes
- Theme token suggestions
Reference: [Uniwind Metro Config](https://docs.uniwind.dev/api/metro-config)
### 1.4 Enable Debug Mode During Development
**Impact: HIGH (identifies unsupported CSS properties before they cause runtime issues)**
Debug mode logs warnings for unsupported CSS properties and invalid classNames. This catches issues at build time rather than runtime.
**Incorrect (no debug feedback):**
```javascript
// metro.config.js
module.exports = withUniwindConfig(config, {
cssEntryFile: './src/global.css',
// Web-specific CSS silently ignored, no feedback
})
```
**Correct (debug enabled in development):**
```javascript
// metro.config.js
module.exports = withUniwindConfig(config, {
cssEntryFile: './src/global.css',
debug: __DEV__, // Only in development
})
```
**What debug mode catches:**
- Web-specific CSS properties (`float`, `cursor`, etc.)
- Invalid className syntax
- Missing theme variables
- Unsupported pseudo-classes (`hover:`, `visited:`)
**Disable in production:**
```javascript
debug: process.env.NODE_ENV !== 'production',
```
Reference: [Uniwind Metro Config](https://docs.uniwind.dev/api/metro-config)
### 1.5 Place CSS Entry File in App Root Directory
**Impact: CRITICAL (wrong placement causes className scanning to miss files)**
Tailwind scans for classNames starting from the CSS entry file's directory. Wrong placement causes missing styles.
**Incorrect (CSS file isolated from components):**
```text
project/
├── config/
│ └── global.css # Tailwind won't scan src/
├── src/
│ └── components/
│ └── Button.tsx # classNames not detected
```
**Correct (CSS file in app root):**
```text
project/
├── src/
│ ├── global.css # Tailwind scans entire src/ tree
│ └── components/
│ └── Button.tsx # classNames detected
```
```css
/* src/global.css */
@import 'tailwindcss';
@import 'uniwind';
/* Your theme and custom styles */
```
**For monorepos with components outside the CSS directory:**
```css
/* src/global.css */
@import 'tailwindcss';
@import 'uniwind';
@source '../../packages/ui/src'; /* Include external packages */
```
Reference: [Uniwind Metro Config](https://docs.uniwind.dev/api/metro-config)
### 1.6 Restart Metro After Configuration Changes
**Impact: CRITICAL (stale cache causes new themes and classes to be ignored)**
Metro caches configuration at startup. Changes to themes, breakpoints, or metro.config.js require a full restart with cache clear.
**Incorrect (just saving files after changes):**
```bash
# Made changes to metro.config.js or global.css
# Expecting hot reload to pick up new themes...
# New theme variables are undefined, styles missing
```
**Correct (restart with cache clear):**
```bash
# After any configuration changes:
npx expo start --clear
# Or for bare React Native:
npx react-native start --reset-cache
```
**Changes that require Metro restart:**
- Adding themes to `extraThemes` array
- Modifying CSS `@theme` variables
- Changing `cssEntryFile` or `dtsFile` paths
- Adding `@source` directives for monorepos
- Modifying custom breakpoints
**Debug tip:** If themes don't appear after restart, verify:
1. Theme is registered in `extraThemes`
2. Theme has `@variant` block in CSS
3. All themes define the same CSS variables
Reference: [Uniwind Metro Config](https://docs.uniwind.dev/api/metro-config)
---
## 2. Theme Architecture
**Impact: CRITICAL**
CSS variables, custom themes, and theming patterns affect every styled component. Poor theme setup causes inconsistent UI, runtime overhead, and broken dark mode.
### 2.1 Define Identical Variables Across All Themes
**Impact: CRITICAL (missing variables cause undefined colors and broken UI)**
Every theme must define the same CSS variables. Missing variables cause undefined colors and Uniwind will warn in development mode.
**Incorrect (inconsistent variables):**
```css
@layer theme {
:root {
@variant light {
--color-background: #ffffff;
--color-foreground: #000000;
--color-primary: #3b82f6;
}
@variant dark {
--color-background: #000000;
--color-foreground: #ffffff;
/* Missing --color-primary! Will be undefined in dark mode */
}
}
}
```
**Correct (all variables defined):**
```css
@layer theme {
:root {
@variant light {
--color-background: #ffffff;
--color-foreground: #000000;
--color-primary: #3b82f6;
--color-secondary: #6b7280;
--color-accent: #f59e0b;
}
@variant dark {
--color-background: #0a0a0a;
--color-foreground: #fafafa;
--color-primary: #60a5fa;
--color-secondary: #9ca3af;
--color-accent: #fbbf24;
}
}
}
```
**Uniwind validation:**
- Warns in `__DEV__` mode when variables are missing
- Enable `debug: true` for detailed variable reports
Reference: [Uniwind Custom Themes](https://docs.uniwind.dev/theming/custom-themes)
### 2.2 Define Theme Variables with @theme Directive
**Impact: CRITICAL (enables semantic color classes across entire app)**
Uniwind uses Tailwind 4's @theme directive for CSS variables. This enables semantic classes like `bg-background` and `text-foreground`.
**Incorrect (hardcoded colors everywhere):**
```tsx
{/* Repeated throughout app, hard to maintain */}
```
**Correct (semantic theme variables):**
```css
/* global.css */
@import 'tailwindcss';
@import 'uniwind';
@theme {
--color-background: #ffffff;
--color-foreground: #000000;
--color-primary: #3b82f6;
--color-muted: #6b7280;
}
```
```tsx
{/* Automatically adapts to theme */}
```
**Benefits:**
- Single source of truth for colors
- Easy theme switching
- Consistent design system
- No JavaScript theme provider needed
Reference: [Tailwind CSS Theme Variables](https://tailwindcss.com/docs/theme)
### 2.3 Register Custom Themes in Metro Config
**Impact: CRITICAL (unregistered themes are not compiled and won't work at runtime)**
Custom themes beyond light/dark must be registered in Metro's `extraThemes` array. Unregistered themes won't be compiled.
**Incorrect (theme defined but not registered):**
```css
/* global.css */
@layer theme {
:root {
@variant ocean {
--color-background: #0c4a6e;
--color-foreground: #e0f2fe;
}
}
}
```
```javascript
// metro.config.js
module.exports = withUniwindConfig(config, {
cssEntryFile: './src/global.css',
// extraThemes missing - 'ocean' won't work!
})
```
**Correct (theme registered):**
```javascript
// metro.config.js
module.exports = withUniwindConfig(config, {
cssEntryFile: './src/global.css',
extraThemes: ['ocean', 'sunset', 'forest'], // Register all custom themes
})
```
```typescript
// Now you can switch to custom themes
Uniwind.setTheme('ocean')
```
**After adding themes:**
1. Register in `extraThemes`
2. Define `@variant` block in CSS
3. Restart Metro with `--clear` flag
Reference: [Uniwind Custom Themes](https://docs.uniwind.dev/theming/custom-themes)
### 2.4 Remove ThemeProvider Wrapper from App
**Impact: HIGH (eliminates unnecessary context and re-renders)**
Uniwind handles themes via CSS variables, not React context. Remove NativeWind's ThemeProvider to avoid unnecessary wrapper and re-renders.
**Incorrect (keeping NativeWind's ThemeProvider):**
```tsx
import { ThemeProvider } from 'nativewind'
export default function App() {
return (
)
}
```
**Correct (no theme provider needed):**
```tsx
export default function App() {
return (
)
}
```
**Switch themes programmatically:**
```typescript
import { Uniwind } from 'uniwind'
import { useColorScheme } from 'react-native'
function useSystemTheme() {
const colorScheme = useColorScheme()
useEffect(() => {
Uniwind.setTheme(colorScheme ?? 'light')
}, [colorScheme])
}
```
**Keep React Navigation's theme:**
```tsx
// NavigationContainer theme is still needed for navigation UI
```
Reference: [Uniwind Migration Guide](https://docs.uniwind.dev/migration-from-nativewind)
### 2.5 Use @variant Blocks for Theme Definitions
**Impact: CRITICAL (enables dark mode and custom themes without JavaScript)**
Each theme requires an `@variant` block defining its CSS variables. Themes switch by changing CSS variables, not JavaScript state.
**Incorrect (JavaScript-based theming):**
```tsx
// Requires context, re-renders, and manual color mapping
const ThemeProvider = ({ children }) => {
const [theme, setTheme] = useState('light')
return (
{children}
)
}
```
**Correct (CSS-based theming):**
```css
/* global.css */
@import 'tailwindcss';
@import 'uniwind';
@layer theme {
:root {
@variant light {
--color-background: #ffffff;
--color-foreground: #0a0a0a;
--color-primary: #3b82f6;
}
@variant dark {
--color-background: #0a0a0a;
--color-foreground: #fafafa;
--color-primary: #60a5fa;
}
}
}
```
```tsx
// No ThemeProvider needed!
Adapts to system theme
```
**Switch themes programmatically:**
```typescript
import { Uniwind } from 'uniwind'
Uniwind.setTheme('dark')
```
Reference: [Uniwind Custom Themes](https://docs.uniwind.dev/theming/custom-themes)
### 2.6 Use light-dark() Function for Adaptive Colors
**Impact: HIGH (automatically switches colors based on active theme)**
The CSS `light-dark()` function automatically selects values based on the active color scheme, reducing theme boilerplate.
**Incorrect (duplicating values in each variant):**
```css
@layer theme {
:root {
@variant light {
--color-surface: #ffffff;
--color-border: #e5e7eb;
}
@variant dark {
--color-surface: #1f2937;
--color-border: #374151;
}
}
}
```
**Correct (using light-dark function):**
```css
@theme {
--color-surface: light-dark(#ffffff, #1f2937);
--color-border: light-dark(#e5e7eb, #374151);
--color-shadow: light-dark(
rgba(0, 0, 0, 0.1),
rgba(0, 0, 0, 0.5)
);
}
```
**When to use light-dark():**
- Simple two-theme setups (light/dark only)
- Reducing CSS duplication
- Inline adaptive values
**When to use @variant blocks:**
- More than two themes
- Complex theme-specific logic
- Different variable sets per theme
Reference: [Uniwind CSS Parser](https://docs.uniwind.dev/api/css)
### 2.7 Use OKLCH Color Space for Perceptual Uniformity
**Impact: HIGH (creates more visually consistent color palettes across themes)**
OKLCH provides perceptually uniform colors. Changing lightness or chroma produces visually consistent results, unlike hex or RGB.
**Incorrect (RGB-based colors with inconsistent contrast):**
```css
@theme {
--color-primary-light: #60a5fa; /* Looks okay */
--color-primary: #3b82f6; /* Looks darker than expected */
--color-primary-dark: #2563eb; /* Jump in perceived darkness */
}
```
**Correct (OKLCH for uniform perception):**
```css
@theme {
/* OKLCH: lightness (0-100%), chroma, hue */
--color-primary-light: oklch(75% 0.15 250);
--color-primary: oklch(60% 0.15 250);
--color-primary-dark: oklch(45% 0.15 250);
/* Consistent 15% lightness steps */
}
```
**Benefits of OKLCH:**
- Predictable lightness gradients
- Better accessibility contrast ratios
- Easier to generate color scales programmatically
- More consistent across different displays
**When to use:**
- Building design systems
- Creating accessible color palettes
- Generating hover/active state variations
Reference: [OKLCH Color Picker](https://oklch.com/)
---
## 3. Component Integration
**Impact: HIGH**
withUniwind wrapper, third-party component styling, and className bindings. Incorrect integration causes missing styles, broken props, or unnecessary re-renders.
### 3.1 Define Wrapped Components at Module Level
**Impact: HIGH (prevents wrapper recreation on every render)**
Create wrapped components outside of render functions. Defining inside causes new wrapper creation on every render.
**Incorrect (wrapper created every render):**
```tsx
function MyScreen() {
// New wrapper created on every render!
const StyledSlider = withUniwind(ThirdPartySlider)
return
}
```
**Correct (wrapper defined at module level):**
```tsx
// styled.ts
import { withUniwind } from 'uniwind'
import { ThirdPartySlider } from 'some-library'
import { ThirdPartyChart } from 'another-library'
export const StyledSlider = withUniwind(ThirdPartySlider)
export const StyledChart = withUniwind(ThirdPartyChart)
```
```tsx
// MyScreen.tsx
import { StyledSlider, StyledChart } from './styled'
function MyScreen() {
return (
<>
>
)
}
```
**Best practice:** Create a centralized `styled.ts` file for all wrapped components.
Reference: [Uniwind withUniwind API](https://docs.uniwind.dev/api/with-uniwind)
### 3.2 Reanimated Components Work Without withUniwind
**Impact: MEDIUM-HIGH (avoids unnecessary wrapping of animated components)**
React Native Reanimated's components are built on core RN components and support `className` automatically. Don't wrap them with `withUniwind`.
**Incorrect (unnecessary wrapping):**
```tsx
import { withUniwind } from 'uniwind'
import Animated from 'react-native-reanimated'
// Unnecessary - adds overhead without benefit
const AnimatedView = withUniwind(Animated.View)
```
**Correct (use directly):**
```tsx
import Animated from 'react-native-reanimated'
function AnimatedCard() {
const animatedStyle = useAnimatedStyle(() => ({
transform: [{ scale: scale.value }],
}))
return (
)
}
```
**Components that work without wrapping:**
- `Animated.View`
- `Animated.Text`
- `Animated.Image`
- `Animated.ScrollView`
- Any component created with `Animated.createAnimatedComponent()`
Reference: [Uniwind withUniwind API](https://docs.uniwind.dev/api/with-uniwind)
### 3.3 Use accent-* Classes for Color Prop Extraction
**Impact: HIGH (correctly extracts color values for non-style props)**
When extracting color values for props (not styles), use `accent-*` prefixed classes. Standard `text-*` or `bg-*` classes won't work for value extraction.
**Incorrect (using text-* for color extraction):**
```tsx
const StyledIcon = withUniwind(Icon, {
color: {
fromClassName: 'colorClassName',
styleProperty: 'color',
},
})
// text-primary doesn't work for color prop extraction
```
**Correct (using accent-* for extraction):**
```tsx
const StyledIcon = withUniwind(Icon, {
color: {
fromClassName: 'colorClassName',
styleProperty: 'accentColor', // Note: accentColor, not color
},
})
// accent-* classes work for value extraction
```
**Pattern for SVG components:**
```tsx
export const SvgIcon = withUniwind(BaseSvgIcon, {
stroke: {
fromClassName: 'strokeClassName',
styleProperty: 'accentColor',
},
fill: {
fromClassName: 'fillClassName',
styleProperty: 'accentColor',
},
})
```
```tsx
```
Reference: [Uniwind withUniwind API](https://docs.uniwind.dev/api/with-uniwind)
### 3.4 Use Custom CSS Classes for Complex Reusable Styles
**Impact: MEDIUM-HIGH (reduces className verbosity for complex component styles)**
Define custom CSS classes for complex, frequently used component styles. Combine them with Tailwind utilities in className.
**Incorrect (verbose repeated utility classes):**
```tsx
// Repeated across many files
Title
Content
```
**Correct (custom CSS class):**
```css
/* global.css */
.card {
flex: 1;
background-color: var(--color-card);
border-radius: 16px;
border-width: 1px;
border-color: var(--color-border);
padding: 24px;
margin-horizontal: 16px;
margin-vertical: 8px;
}
.card-title {
font-size: 18px;
font-weight: 600;
color: var(--color-foreground);
margin-bottom: 8px;
}
```
```tsx
// Clean, readable component
Title
Content
```
**Best practices:**
- Use flat selectors (no nesting)
- Reference theme variables for consistency
- Combine custom classes with utility overrides
Reference: [Uniwind CSS Parser](https://docs.uniwind.dev/api/css)
### 3.5 Use Custom Prop Mappings for Non-Style Props
**Impact: HIGH (enables Tailwind classes for color, width, and other non-style props)**
Some components accept props like `color` or `size` as values, not styles. Use custom mappings to extract specific values from Tailwind classes.
**Incorrect (trying to use className for color prop):**
```tsx
import { Icon } from 'some-icon-library'
// Icon expects color="#3b82f6", not a style object
```
**Correct (custom prop mapping):**
```tsx
// styled.ts
import { withUniwind } from 'uniwind'
import { Icon as BaseIcon } from 'some-icon-library'
export const Icon = withUniwind(BaseIcon, {
color: {
fromClassName: 'colorClassName',
styleProperty: 'color', // Extract color value
},
size: {
fromClassName: 'sizeClassName',
styleProperty: 'width', // Extract width value as size
},
})
```
```tsx
// Now use accent-* classes for color extraction
```
**Common mappings:**
- `color` → `colorClassName` with `accent-*` classes
- `strokeColor` / `fillColor` for SVGs
- `size` from width classes
Reference: [Uniwind withUniwind API](https://docs.uniwind.dev/api/with-uniwind)
### 3.6 Use withUniwind for Third-Party Components
**Impact: HIGH (enables className support on components that only accept style props)**
Third-party components that don't natively support `className` need to be wrapped with `withUniwind`.
**Incorrect (className ignored on third-party component):**
```tsx
import { CustomSlider } from 'some-library'
// className prop is ignored, no styles applied
```
**Correct (wrapped with withUniwind):**
```tsx
// styled.ts - define wrappers at module level
import { withUniwind } from 'uniwind'
import { CustomSlider as BaseSlider } from 'some-library'
export const CustomSlider = withUniwind(BaseSlider)
```
```tsx
// Component.tsx
import { CustomSlider } from './styled'
// Now className works!
```
**When NOT needed:**
- React Native core components (View, Text, etc.)
- Components built on View/Text that forward style prop
- Libraries that already support className
Reference: [Uniwind withUniwind API](https://docs.uniwind.dev/api/with-uniwind)
---
## 4. Responsive Design
**Impact: HIGH**
Breakpoints, media queries, and mobile-first patterns. Wrong approaches cause layout breaks across device sizes and inconsistent spacing.
### 4.1 Define Custom Breakpoints with Semantic Names
**Impact: MEDIUM-HIGH (improves code readability and matches design specifications)**
Override default breakpoints or add new ones using the @theme directive. Use meaningful names like `tablet` instead of arbitrary values.
**Incorrect (using arbitrary values inline):**
```tsx
// Hard to understand what 834px means
```
**Correct (semantic custom breakpoints):**
```css
/* global.css */
@theme {
/* Override defaults */
--breakpoint-sm: 640px;
--breakpoint-md: 768px;
--breakpoint-lg: 1024px;
/* Add semantic names */
--breakpoint-tablet: 768px;
--breakpoint-desktop: 1024px;
--breakpoint-ultrawide: 1920px;
}
```
```tsx
{/* Clear intent: tablet and desktop layouts */}
```
**Naming conventions:**
- `phone` / `tablet` / `desktop` - Device categories
- `compact` / `regular` / `expanded` - iOS size classes
- `portrait` / `landscape` - Orientation (if needed)
Reference: [Uniwind Responsive Breakpoints](https://docs.uniwind.dev/breakpoints)
### 4.2 Design Mobile-First with Progressive Enhancement
**Impact: HIGH (ensures optimal experience on smallest screens first)**
Uniwind uses mobile-first breakpoints. Unprefixed utilities apply to all screens; prefixed utilities apply at that breakpoint and above.
**Incorrect (desktop-first approach):**
```tsx
// Starting with large screen, then overriding for mobile
{/* Confusing: base is desktop, overrides for mobile */}
```
**Correct (mobile-first approach):**
```tsx
// Start mobile, enhance for larger screens
{/* Clear: mobile base, progressive enhancement */}
```
**Mobile-first pattern:**
```tsx
{/* Responsive width */}
```
**Default breakpoints:**
- `sm:` - 640px and up
- `md:` - 768px and up
- `lg:` - 1024px and up
- `xl:` - 1280px and up
Reference: [Uniwind Responsive Breakpoints](https://docs.uniwind.dev/breakpoints)
### 4.3 Limit Breakpoints to 3-5 for Maintainability
**Impact: HIGH (reduces complexity and testing burden)**
Using too many breakpoints creates complex, hard-to-test layouts. Focus on 3-5 key breakpoints that match your target devices.
**Incorrect (too many breakpoints):**
```tsx
{/* 7 breakpoints - hard to maintain and test */}
```
**Correct (focused breakpoints):**
```tsx
{/* 3 breakpoints - clear and testable */}
```
**Recommended breakpoint strategy:**
| Breakpoint | Target | Use Case |
|------------|--------|----------|
| (none) | < 640px | Phones |
| `sm:` | 640px+ | Large phones, small tablets |
| `md:` or `lg:` | 768px-1024px | Tablets |
| `xl:` | 1280px+ | Desktop (web) |
Reference: [Uniwind Responsive Breakpoints](https://docs.uniwind.dev/breakpoints)
### 4.4 Scale Spacing and Typography Responsively
**Impact: MEDIUM-HIGH (creates visually balanced layouts across screen sizes)**
Adjust spacing, padding, and font sizes based on screen size. Larger screens need more whitespace to avoid cramped layouts.
**Incorrect (fixed spacing on all screens):**
```tsx
// Same tight spacing on phone and tablet
Title
Description
```
**Correct (responsive scaling):**
```tsx
Title
Description
```
**Spacing scale recommendation:**
| Element | Phone | Tablet | Desktop |
|---------|-------|--------|---------|
| Container padding | p-4 | p-6 | p-8 |
| Card padding | p-3 | p-4 | p-6 |
| Section gap | gap-4 | gap-6 | gap-8 |
| List item gap | gap-2 | gap-3 | gap-4 |
Reference: [Uniwind Responsive Breakpoints](https://docs.uniwind.dev/breakpoints)
### 4.5 Use hidden/flex for Responsive Visibility
**Impact: HIGH (cleanly shows/hides content across breakpoints)**
Toggle element visibility across breakpoints using `hidden` and display utilities. This is cleaner than conditional rendering.
**Incorrect (JavaScript conditional rendering):**
```tsx
function Header() {
const isDesktop = useMediaQuery('(min-width: 1024px)')
return (
{isDesktop ? : }
)
}
```
**Correct (CSS visibility toggle):**
```tsx
function Header() {
return (
{/* Mobile menu - visible on mobile, hidden on desktop */}
{/* Desktop nav - hidden on mobile, visible on desktop */}
Home
About
Contact
)
}
```
**Common patterns:**
```tsx
// Show only on mobile
// Show only on tablet and up
// Show only on desktop
```
Reference: [Uniwind Responsive Breakpoints](https://docs.uniwind.dev/breakpoints)
---
## 5. Performance Optimization
**Impact: MEDIUM-HIGH**
Runtime style resolution, dynamic classNames, and render optimization. Impacts FPS, app responsiveness, and memory usage on lower-end devices.
### 5.1 Combine className and style Prop Correctly
**Impact: MEDIUM (ensures both static and dynamic styles apply properly)**
When combining className with style prop (for animations or dynamic values), both apply with style taking precedence for conflicts.
**Incorrect (style overwriting all className styles):**
```tsx
function AnimatedCard({ scale }: { scale: number }) {
// Trying to pass object directly, won't work as expected
return (
)
}
```
**Correct (proper style array with Reanimated):**
```tsx
import Animated, { useAnimatedStyle } from 'react-native-reanimated'
function AnimatedCard({ scale }: { scale: SharedValue }) {
const animatedStyle = useAnimatedStyle(() => ({
transform: [{ scale: scale.value }],
}))
return (
)
}
```
**Combining with StyleSheet:**
```tsx
import { useResolveClassNames } from 'uniwind'
function Card() {
const tailwindStyles = useResolveClassNames('p-4 rounded-lg')
const customStyles = StyleSheet.create({
shadow: {
shadowColor: '#000',
shadowOffset: { width: 0, height: 2 },
shadowOpacity: 0.25,
elevation: 5,
},
})
return (
Content
)
}
```
Reference: [Uniwind useResolveClassNames](https://docs.uniwind.dev/api/use-resolve-class-names)
### 5.2 Memoize Variant Style Objects
**Impact: MEDIUM-HIGH (prevents object recreation on every render)**
When using variant mapping objects, define them outside components or memoize them to prevent recreation on every render.
**Incorrect (object recreated every render):**
```tsx
function Button({ variant, children }: ButtonProps) {
// New object created on every render
const variants = {
primary: 'bg-primary text-primary-foreground',
secondary: 'bg-secondary text-secondary-foreground',
destructive: 'bg-destructive text-destructive-foreground',
}
return (
{children}
)
}
```
**Correct (object defined outside component):**
```tsx
const buttonVariants = {
primary: 'bg-primary text-primary-foreground',
secondary: 'bg-secondary text-secondary-foreground',
destructive: 'bg-destructive text-destructive-foreground',
} as const
function Button({ variant, children }: ButtonProps) {
return (
{children}
)
}
```
**Alternative (useMemo for computed variants):**
```tsx
function Button({ variant, size, disabled, children }: ButtonProps) {
const className = useMemo(() => {
const base = 'rounded font-medium'
const variantStyle = buttonVariants[variant]
const sizeStyle = sizeVariants[size]
const disabledStyle = disabled ? 'opacity-50' : ''
return `${base} ${variantStyle} ${sizeStyle} ${disabledStyle}`
}, [variant, size, disabled])
return ...
}
```
### 5.3 Prefer className Over Inline style Prop
**Impact: MEDIUM (enables build-time optimization and consistent design tokens)**
Use className for styling whenever possible. Inline style props bypass build-time optimization and can't use theme variables.
**Incorrect (inline styles):**
```tsx
function Card() {
return (
Title
)
}
```
**Correct (className with theme tokens):**
```tsx
function Card() {
return (
Title
)
}
```
**When inline styles ARE appropriate:**
```tsx
// Animated values from Reanimated
// Dynamic values from props or calculations
```
**Benefits of className:**
- Theme variable access
- Build-time compilation
- TypeScript autocomplete
- Consistent design tokens
### 5.4 Use Complete Static Class Names for Build-Time Resolution
**Impact: MEDIUM-HIGH (enables build-time compilation, 2.5× faster than runtime resolution)**
Uniwind compiles static class names at build time. Dynamic string construction bypasses the compiler, forcing slower runtime resolution.
**Incorrect (dynamic class construction):**
```tsx
function Badge({ color }: { color: 'red' | 'green' | 'blue' }) {
// Tailwind compiler can't detect these classes!
return (
Badge
)
}
```
**Correct (complete static class names):**
```tsx
const colorStyles = {
red: 'bg-red-500 text-red-900',
green: 'bg-green-500 text-green-900',
blue: 'bg-blue-500 text-blue-900',
} as const
function Badge({ color }: { color: keyof typeof colorStyles }) {
return (
Badge
)
}
```
**Why this matters:**
- Uniwind precomputes styles at build time
- Dynamic construction forces runtime parsing
- Build-time resolution is ~2.5× faster (81ms vs 197ms)
**Rule of thumb:** If you can grep for the class name in your source code, it will be compiled.
Reference: [Uniwind Performance](https://uniwind.dev/)
### 5.5 Use tailwind-merge for Class Deduplication
**Impact: MEDIUM (prevents conflicting utilities from both applying)**
Uniwind doesn't automatically deduplicate conflicting utilities. Use `tailwind-merge` to ensure only the last conflicting class applies.
**Incorrect (both classes apply based on CSS specificity):**
```tsx
function Card({ className }: { className?: string }) {
// Both bg-card AND bg-red-500 may apply!
return (
Content
)
}
// Usage - unpredictable which background wins
```
**Correct (using tailwind-merge):**
```tsx
import { twMerge } from 'tailwind-merge'
function Card({ className }: { className?: string }) {
return (
Content
)
}
// Usage - bg-red-500 correctly overrides bg-card
```
**When to use tailwind-merge:**
- Components that accept className prop for overrides
- Composing multiple class sources
- Building component libraries
**Performance tip:** twMerge has minimal overhead for typical use cases.
```bash
npm install tailwind-merge
```
Reference: [Uniwind Migration Guide](https://docs.uniwind.dev/migration-from-nativewind)
### 5.6 Use useResolveClassNames Sparingly
**Impact: MEDIUM-HIGH (runtime resolution is slower than build-time compilation)**
The `useResolveClassNames` hook resolves styles at runtime. Use it only when className prop isn't available, as it's slower than build-time compilation.
**Incorrect (using hook for standard components):**
```tsx
function Card() {
// Unnecessary runtime resolution
const styles = useResolveClassNames('bg-card rounded-xl p-4')
return (
Content
)
}
```
**Correct (use className directly):**
```tsx
function Card() {
// Build-time compiled, faster
return (
Content
)
}
```
**When useResolveClassNames IS appropriate:**
```tsx
// Library configuration that requires style objects
import { NavigationContainer, DefaultTheme } from '@react-navigation/native'
function App() {
const cardStyle = useResolveClassNames('bg-card')
const textStyle = useResolveClassNames('text-foreground')
const theme = {
...DefaultTheme,
colors: {
...DefaultTheme.colors,
card: cardStyle.backgroundColor,
text: textStyle.color,
},
}
return ...
}
```
Reference: [Uniwind useResolveClassNames](https://docs.uniwind.dev/api/use-resolve-class-names)
---
## 6. Platform Patterns
**Impact: MEDIUM**
iOS/Android selectors, safe area handling, and platform-specific styling. Ensures correct behavior across platforms without conditional code.
### 6.1 Configure Font Families Without Fallbacks
**Impact: MEDIUM (React Native requires exact font file names)**
React Native doesn't support font fallbacks. Specify only the exact font file name, not a stack of fonts.
**Incorrect (web-style font stack):**
```css
@theme {
--font-family-sans: 'Inter', 'Helvetica', 'Arial', sans-serif;
/* React Native will fail to find this font! */
}
```
**Correct (exact font file name):**
```css
@theme {
--font-family-sans: 'Inter-Regular';
--font-family-sans-medium: 'Inter-Medium';
--font-family-sans-bold: 'Inter-Bold';
--font-family-mono: 'FiraCode-Regular';
}
```
**Font weight mapping:**
```css
@theme {
/* Map Tailwind weights to font files */
--font-weight-normal: 'Inter-Regular';
--font-weight-medium: 'Inter-Medium';
--font-weight-semibold: 'Inter-SemiBold';
--font-weight-bold: 'Inter-Bold';
}
```
**Usage in components:**
```tsx
// Use font-sans, font-mono etc.
Regular text
Medium weight
Code text
```
**Loading fonts with Expo:**
```tsx
import { useFonts, Inter_400Regular, Inter_500Medium } from '@expo-google-fonts/inter'
function App() {
const [fontsLoaded] = useFonts({
'Inter-Regular': Inter_400Regular,
'Inter-Medium': Inter_500Medium,
})
if (!fontsLoaded) return null
return
}
```
Reference: [Uniwind Migration Guide](https://docs.uniwind.dev/migration-from-nativewind)
### 6.2 Understand Yoga Layout Engine Differences
**Impact: MEDIUM (prevents confusion from web CSS assumptions)**
React Native uses Yoga, not browser CSS. Key differences affect layout behavior and available properties.
**Incorrect (assuming web CSS behavior):**
```tsx
// These web CSS patterns don't work in React Native!
{/* No floats */}
{/* No CSS grid (work in progress) */}
{/* No hover on mobile */}
{/* No cursor */}
```
**Correct (understanding Yoga defaults):**
```tsx
// All Views are flexbox by default with flexDirection: 'column'
{/* Explicit row layout */}
{/* Flex children */}
```
**Key Yoga differences:**
| Web CSS | Yoga/React Native |
|---------|-------------------|
| `display: block` (default) | `display: flex` (always) |
| `flex-direction: row` | `flex-direction: column` (default) |
| Styles cascade/inherit | Styles don't inherit |
| `em`/`rem` units | Use Uniwind's rem polyfill |
| CSS Grid | Not supported (in progress) |
| `position: fixed` | Use `absolute` + safe areas |
**Unsupported web features:**
- `float`, `clear`
- Pseudo-elements (`::before`, `::after`)
- `hover:`, `visited:` pseudo-classes
Reference: [Uniwind Supported ClassNames](https://docs.uniwind.dev/class-names)
### 6.3 Use Platform Selectors for iOS/Android Differences
**Impact: MEDIUM (eliminates Platform.select boilerplate in components)**
Use `ios:` and `android:` prefixes to apply platform-specific styles without JavaScript conditionals.
**Incorrect (JavaScript Platform.select):**
```tsx
import { Platform, View, Text } from 'react-native'
function Card() {
return (
Content
)
}
```
**Correct (platform selectors):**
```tsx
function Card() {
return (
Content
)
}
```
**Common platform differences:**
```tsx
// Fonts differ by platform
Platform-specific font
// Padding for status bar
Header content
// Shadows vs elevation
Elevated card
```
Reference: [Uniwind Supported ClassNames](https://docs.uniwind.dev/class-names)
### 6.4 Use react-native-safe-area-context for Safe Areas
**Impact: MEDIUM (correctly handles notches, status bars, and home indicators)**
Uniwind doesn't support `*-safe` utility classes. Use `react-native-safe-area-context` for safe area insets.
**Incorrect (NativeWind safe area classes):**
```tsx
// These classes don't work in Uniwind!
Content that avoids notches
```
**Correct (safe area context):**
```tsx
import { useSafeAreaInsets } from 'react-native-safe-area-context'
function Screen() {
const insets = useSafeAreaInsets()
return (
Safe content
)
}
```
**Alternative (SafeAreaView component):**
```tsx
import { SafeAreaView } from 'react-native-safe-area-context'
function Screen() {
return (
Safe content
)
}
```
**App setup required:**
```tsx
import { SafeAreaProvider } from 'react-native-safe-area-context'
function App() {
return (
)
}
```
Reference: [react-native-safe-area-context](https://docs.expo.dev/versions/latest/sdk/safe-area-context/)
### 6.5 Use web: Selector for Cross-Platform Apps
**Impact: MEDIUM (enables web-specific styles without affecting native)**
For apps targeting both native and web (Expo Web, React Native Web), use `web:` prefix for web-specific styles.
**Incorrect (web styles applying to native):**
```tsx
// cursor and hover apply to native where they do nothing
Click me
```
**Correct (platform-specific):**
```tsx
Click me
```
**Common web-specific patterns:**
```tsx
// Web hover effects
Hover to scale on web
// Web-specific layout
Responsive layout
// Web cursor states
Interactive element
```
Reference: [Uniwind Supported ClassNames](https://docs.uniwind.dev/class-names)
---
## 7. State & Interaction
**Impact: MEDIUM**
Pressable states, pseudo-classes, and data selectors for conditional styling. Incorrect patterns cause broken interactive UI and inaccessible components.
### 7.1 Avoid hover: on Native - Use active: Instead
**Impact: MEDIUM (prevents unused styles and reduces bundle size)**
Mobile devices don't have hover states. Use `active:` for touch feedback on native, and `web:hover:` for web targets only.
**Incorrect (hover on native):**
```tsx
// hover: is ignored on iOS/Android
Button
```
**Correct (platform-appropriate states):**
```tsx
Button
```
**Complete interactive pattern:**
```tsx
function InteractiveCard() {
return (
Interactive content
)
}
```
**Ignored pseudo-classes on native:**
- `hover:`
- `visited:`
- Web-specific cursor states
Reference: [Uniwind Supported ClassNames](https://docs.uniwind.dev/class-names)
### 7.2 Use dark: Variant for Dark Mode Styles
**Impact: MEDIUM (eliminates 20+ lines of conditional color logic)**
Use the `dark:` prefix for dark mode overrides. Styles automatically apply based on the active theme.
**Incorrect (manual dark mode logic):**
```tsx
function Card() {
const colorScheme = useColorScheme()
const isDark = colorScheme === 'dark'
return (
Content
)
}
```
**Correct (dark: variant):**
```tsx
function Card() {
return (
Content
)
}
```
**Better (semantic theme variables):**
```tsx
// Using theme variables instead of explicit dark: overrides
function Card() {
return (
Content
)
}
```
**When to use dark: vs theme variables:**
| Use `dark:` | Use theme variables |
|-------------|-------------------|
| One-off overrides | Consistent design system |
| Quick prototyping | Production apps |
| Color exceptions | Standard colors |
Reference: [Uniwind Theming](https://docs.uniwind.dev/theming/custom-themes)
### 7.3 Use Data Selectors for Component State Styling
**Impact: MEDIUM (enables conditional styling based on data attributes)**
Use `data-[prop=value]:` syntax to style based on component data attributes. This enables prop-based conditional styling.
**Incorrect (ternary operators in className):**
```tsx
function ListItem({ isSelected, isHighlighted }: Props) {
return (
Item
)
}
```
**Correct (data selectors):**
```tsx
function ListItem({ isSelected, isHighlighted }: Props) {
return (
Item
)
}
```
**Complex state combinations:**
```tsx
function Tab({ isActive, hasNotification }: TabProps) {
return (
Tab
)
}
```
Reference: [Uniwind Supported ClassNames](https://docs.uniwind.dev/class-names)
### 7.4 Use Group Variants for Parent-Child Styling (WIP)
**Impact: LOW-MEDIUM (reduces 10-20 lines of context boilerplate)**
Group variants allow styling children based on parent state. Note: This feature is work in progress in Uniwind.
**Incorrect (no parent-child state coordination):**
```tsx
// Children don't respond to parent press state
Title doesn't change when card is pressed
Opacity stays the same
```
**Correct (workaround with context):**
```tsx
function Card({ children }: CardProps) {
const [isPressed, setIsPressed] = useState(false)
return (
setIsPressed(true)}
onPressOut={() => setIsPressed(false)}
className="bg-card p-4 rounded"
>
{children}
)
}
function CardTitle() {
const { isPressed } = useCardContext()
return (
Title changes when card is pressed
)
}
```
**Future pattern (when group-* is supported):**
```tsx
// This will work when group variants are implemented
Title changes color when card is pressed
```
**Status:** Group variants are listed as "Work in Progress" in Uniwind documentation.
Reference: [Uniwind Supported ClassNames](https://docs.uniwind.dev/class-names)
### 7.5 Use Pressable with active:/focus:/disabled: States
**Impact: MEDIUM (eliminates 10-15 lines of manual state management)**
Uniwind supports `active:`, `focus:`, and `disabled:` pseudo-classes on Pressable components for touch feedback.
**Incorrect (manual press state management):**
```tsx
function Button({ onPress }: ButtonProps) {
const [isPressed, setIsPressed] = useState(false)
return (
setIsPressed(true)}
onPressOut={() => setIsPressed(false)}
onPress={onPress}
className={isPressed ? 'bg-primary/80' : 'bg-primary'}
>
Press me
)
}
```
**Correct (pseudo-class states):**
```tsx
function Button({ onPress, disabled }: ButtonProps) {
return (
Press me
)
}
```
**Available pseudo-classes:**
| Pseudo-class | Trigger |
|--------------|---------|
| `active:` | While pressed/touched |
| `focus:` | When focused (accessibility) |
| `disabled:` | When disabled prop is true |
**Note:** `hover:` is not supported on native (no mouse). Use `web:hover:` for web targets.
Reference: [Uniwind Supported ClassNames](https://docs.uniwind.dev/class-names)
---
## 8. Migration & Compatibility
**Impact: LOW-MEDIUM**
NativeWind migration, Tailwind 4 syntax, and common pitfalls. Helps teams transition smoothly and avoid breaking changes.
### 8.1 Account for Different rem Default Values
**Impact: LOW-MEDIUM (prevents 14% spacing difference after migration)**
NativeWind uses 14px as rem default, Uniwind uses 16px. Adjust the polyfill if preserving existing spacing.
**Incorrect (ignoring rem difference):**
```javascript
// metro.config.js
module.exports = withUniwindConfig(config, {
cssEntryFile: './src/global.css',
// Using default 16px when app was designed for 14px
})
```
```tsx
// p-4 = 16px in Uniwind, but was 14px in NativeWind
// 14% larger spacing across the entire app!
Spacing is off
```
**Correct (matching NativeWind's rem for migration):**
```javascript
// metro.config.js
module.exports = withUniwindConfig(config, {
cssEntryFile: './src/global.css',
polyfills: {
rem: 14, // Match NativeWind's default
},
})
```
```tsx
// Now p-4 = 14px, matching original design
Spacing matches original
```
**When to use each:**
| Scenario | rem Value |
|----------|-----------|
| Migrating existing NativeWind app | 14px |
| Design system uses 14px base | 14px |
| New Uniwind project | 16px (default) |
| Web app migration | 16px |
Reference: [Uniwind Migration Guide](https://docs.uniwind.dev/migration-from-nativewind)
### 8.2 Follow NativeWind Migration Checklist
**Impact: LOW-MEDIUM (prevents 5-10 common migration errors)**
Migrating from NativeWind to Uniwind requires several configuration changes. Follow this checklist to avoid issues.
**Incorrect (keeping NativeWind configuration):**
```javascript
// babel.config.js - WRONG: NativeWind preset
module.exports = {
presets: ['nativewind/babel'], // Remove this!
}
```
```javascript
// metro.config.js - WRONG: No Uniwind config
const { getDefaultConfig } = require('expo/metro-config')
module.exports = getDefaultConfig(__dirname) // Missing withUniwindConfig!
```
**Correct (Uniwind configuration):**
```javascript
// babel.config.js - No NativeWind preset needed
module.exports = {
presets: ['babel-preset-expo'],
}
```
```javascript
// metro.config.js - Uniwind configuration
const { getDefaultConfig } = require('expo/metro-config')
const { withUniwindConfig } = require('uniwind/metro')
const config = getDefaultConfig(__dirname)
module.exports = withUniwindConfig(config, {
cssEntryFile: './src/global.css',
})
```
**Full migration checklist:**
1. Install: `bun add uniwind tailwindcss && bun remove nativewind`
2. Remove Babel preset from babel.config.js
3. Update Metro config with withUniwindConfig
4. Update CSS: `@import 'tailwindcss'; @import 'uniwind';`
5. Delete nativewind.d.ts
6. Remove ThemeProvider
7. Replace cssInterop with withUniwind
8. Delete tailwind.config.js
Reference: [Uniwind Migration Guide](https://docs.uniwind.dev/migration-from-nativewind)
### 8.3 Replace *-safe Classes with Safe Area Context
**Impact: LOW-MEDIUM (prevents content overlapping notch and home indicator)**
NativeWind's `*-safe` utility classes don't exist in Uniwind. Use `react-native-safe-area-context` instead.
**Incorrect (NativeWind safe area classes):**
```tsx
// These classes don't work in Uniwind!
Content overlaps notch because classes are ignored
```
**Correct (Safe Area Context hook):**
```tsx
import { useSafeAreaInsets } from 'react-native-safe-area-context'
function Screen() {
const insets = useSafeAreaInsets()
return (
Content properly avoids notch
)
}
```
**Alternative (SafeAreaView component):**
```tsx
import { SafeAreaView } from 'react-native-safe-area-context'
function Screen() {
return (
Content with safe area padding
)
}
```
Reference: [react-native-safe-area-context](https://docs.expo.dev/versions/latest/sdk/safe-area-context/)
### 8.4 Replace cssInterop with withUniwind
**Impact: LOW-MEDIUM (prevents runtime errors from incompatible API)**
NativeWind uses `cssInterop` for third-party components. Uniwind uses `withUniwind` with a different API.
**Incorrect (NativeWind's cssInterop):**
```tsx
import { cssInterop } from 'nativewind' // Doesn't exist in Uniwind!
import { LinearGradient } from 'expo-linear-gradient'
cssInterop(LinearGradient, {
className: 'style',
contentContainerClassName: 'contentContainerStyle',
})
// Runtime error: cssInterop is not a function
```
**Correct (Uniwind's withUniwind):**
```tsx
import { withUniwind } from 'uniwind'
import { LinearGradient as BaseLinearGradient } from 'expo-linear-gradient'
export const LinearGradient = withUniwind(BaseLinearGradient)
// For custom prop mappings
export const LinearGradientWithProps = withUniwind(BaseLinearGradient, {
contentContainerStyle: {
fromClassName: 'contentContainerClassName',
},
})
```
**Key API differences:**
| NativeWind `cssInterop` | Uniwind `withUniwind` |
|------------------------|---------------------|
| Mutates globally | Returns new component |
| Called once at setup | Define at module level |
| Maps className to style | className works by default |
Reference: [Uniwind withUniwind API](https://docs.uniwind.dev/api/with-uniwind)
### 8.5 Use Tailwind 4 CSS-First Configuration
**Impact: LOW-MEDIUM (enables proper theming and eliminates tailwind.config.js)**
Uniwind requires Tailwind 4 syntax. Configuration moves from JavaScript (tailwind.config.js) to CSS (@theme directive).
**Incorrect (Tailwind 3 JavaScript config):**
```javascript
// tailwind.config.js - DON'T USE THIS
module.exports = {
theme: {
extend: {
colors: {
primary: '#3b82f6',
background: '#ffffff',
},
},
},
}
```
**Correct (Tailwind 4 CSS config):**
```css
/* global.css */
@import 'tailwindcss';
@import 'uniwind';
@theme {
--color-primary: #3b82f6;
--color-background: #ffffff;
--color-foreground: #0a0a0a;
/* Custom spacing */
--spacing-18: 4.5rem;
/* Custom breakpoints */
--breakpoint-tablet: 768px;
}
```
**Key differences:**
| Tailwind 3 | Tailwind 4 |
|------------|------------|
| `tailwind.config.js` | `@theme` in CSS |
| `theme.extend.colors` | `--color-*` variables |
| `theme.extend.spacing` | `--spacing-*` variables |
| `theme.screens` | `--breakpoint-*` variables |
| JavaScript module | Pure CSS |
Reference: [Tailwind CSS v4 Theme](https://tailwindcss.com/docs/theme)
---
## References
1. [https://docs.uniwind.dev](https://docs.uniwind.dev)
2. [https://github.com/uni-stack/uniwind](https://github.com/uni-stack/uniwind)
3. [https://uniwind.dev](https://uniwind.dev)
4. [https://tailwindcss.com/docs/theme](https://tailwindcss.com/docs/theme)
5. [https://docs.expo.dev/versions/latest/sdk/safe-area-context/](https://docs.expo.dev/versions/latest/sdk/safe-area-context/)
6. [https://reactnative.dev](https://reactnative.dev)
---
## Source Files
This document was compiled from individual reference files. For detailed editing or extension:
| File | Description |
|------|-------------|
| [references/_sections.md](references/_sections.md) | Category definitions and impact ordering |
| [assets/templates/_template.md](assets/templates/_template.md) | Template for creating new rules |
| [SKILL.md](SKILL.md) | Quick reference entry point |
| [metadata.json](metadata.json) | Version and reference URLs |