Skip to main content

Common Questions

Custom fonts require two steps: loading the font files into your app and configuring the font names in your CSS. Uniwind maps className props to font families, but the actual font files need to be included separately.
Important: Uniwind only handles the mapping of classNames to font families. You must include and load the font files separately using Expo Font or React Native’s asset system.

Expo Projects

Step 1: Install and configure expo-font

Add the font files to your project and configure them in app.json:
app.json
{
  "expo": {
    "plugins": [
      [
        "expo-font",
        {
          "fonts": [
            "./assets/fonts/Roboto-Regular.ttf",
            "./assets/fonts/Roboto-Medium.ttf",
            "./assets/fonts/Roboto-Bold.ttf",
            "./assets/fonts/FiraCode-Regular.ttf"
          ]
        }
      ]
    ]
  }
}
Place your font files in the assets/fonts directory or any directory structure that works for your project. Just make sure the paths in app.json match your actual file locations.

Step 2: Define font families in global.css

Configure your font families and text sizes using CSS variables in the @theme directive:
global.css
@import 'tailwindcss';
@import 'uniwind';

@theme {
  /* Other values */
  /* ... */

  /* Font families */
  --font-sans: 'Roboto-Regular';
  --font-sans-medium: 'Roboto-Medium';
  --font-sans-bold: 'Roboto-Bold';
  --font-mono: 'FiraCode-Regular';
}
The font family names in your CSS must exactly match the font file names (without the extension). For example, Roboto-Regular.ttf becomes 'Roboto-Regular'.

Step 3: Use font classes in your components

Now you can use the configured font families with Tailwind classes:
import { Text } from 'react-native'

export const CustomFontExample = () => (
  <>
    <Text className="font-sans text-base">
      Regular text using Roboto-Regular
    </Text>

    <Text className="font-sans-medium text-lg">
      Medium weight using Roboto-Medium
    </Text>

    <Text className="font-sans-bold text-xl">
      Bold text using Roboto-Bold
    </Text>

    <Text className="font-mono text-sm">
      Monospace text using FiraCode-Regular
    </Text>
  </>
)

Bare React Native Projects

For bare React Native projects without Expo, you can include fonts using the react-native.config.js file:

Step 1: Create react-native.config.js

react-native.config.js
module.exports = {
  project: {
    ios: {},
    android: {},
  },
  assets: ['./assets/fonts'],
};
Run the following command to link your fonts:
npx react-native-asset
This will copy your font files to the native iOS and Android projects.

Step 3: Configure in global.css

After linking the fonts, configure them in your global.css the same way as Expo projects:
global.css
@import 'tailwindcss';
@import 'uniwind';

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

Platform-Specific Fonts

You can define different fonts for different platforms using media queries:
global.css
@layer theme {
  :root {
    /* Default fonts */
    --font-sans: 'Roboto-Regular';

    /* iOS-specific fonts */
    @media ios {
      --font-sans: 'SF Pro Text';
    }

    /* Android-specific fonts */
    @media android {
      --font-sans: 'Roboto-Regular';
    }

    /* Web-specific fonts */
    @media web {
      --font-sans: 'system-ui', sans-serif;
    }
  }
}

Troubleshooting

Fonts not loading

If your fonts aren’t appearing:
  1. Check font file names - Make sure the font family name in CSS matches the font file name exactly
  2. Rebuild the app - Font changes require a full rebuild, not just a Metro refresh
  3. Verify file paths - Ensure the paths in app.json or react-native.config.js are correct
  4. Clear cache - Try clearing Metro bundler cache with npx expo start --clear

Font looks different than expected

React Native doesn’t support dynamic font weights. Each weight requires its own font file. Make sure you’ve:
  • Included all the font weight variants you need
  • Mapped each variant to a CSS variable in @theme
  • Used the correct className for each weight

Platform Selectors

Learn more about using platform-specific styles
When using Expo Router, it’s recommended to place your global.css file in the project root and import it in your root layout file.

Step 1: Create global.css in the project root

Place your global.css file in the root of your project:
your-project/
├── app/
│   ├── _layout.tsx
│   └── index.tsx
├── components/
├── global.css         // ✅ Put it here
└── package.json
global.css
@import 'tailwindcss';
@import 'uniwind';

/* Your custom CSS and themes */

Step 2: Import in your root layout

Import the CSS file in your root layout file (app/_layout.tsx):
app/_layout.tsx
import '../global.css' // Import at the top

export default function RootLayout() {
  // Your layout code
}
Importing in the root _layout.tsx ensures the CSS is loaded before any of your app screens render, and enables hot reload when you modify styles.

Step 3: Configure Metro

Point Metro to your CSS file:
metro.config.js
module.exports = withUniwindConfig(config, {
  cssEntryFile: './global.css',
});

Why This Structure?

  • No @source needed: Tailwind scans from the project root, so it automatically finds app and components directories
  • Simpler setup: No need to manually configure which directories to scan
  • Standard convention: Matches typical React Native project structure
With global.css in the root, Tailwind will automatically scan all directories (app, components, etc.) without needing @source directives.

Alternative: App Directory

You can also place global.css inside the app directory:
your-project/
├── app/
│   ├── _layout.tsx
│   ├── global.css     // Alternative location
│   └── index.tsx
├── components/
└── package.json
Then import it in _layout.tsx:
app/_layout.tsx
import './global.css' // Note: different path

export default function RootLayout() {
  // Your layout code
}
And update Metro config:
metro.config.js
module.exports = withUniwindConfig(config, {
  cssEntryFile: './app/global.css',
});
Important: If you place global.css in the app directory and have components outside (like a components folder), you must use @source to include them:
app/global.css
@import 'tailwindcss';
@import 'uniwind';

@source '../components';
The location of global.css determines your app root. Tailwind will only scan for classNames starting from that directory.

Global CSS Location Guide

Learn more about configuring global.css

Monorepos & @source

Understand how @source works with multiple directories
If you’re experiencing full app reloads when modifying CSS, even though you followed the documentation and didn’t import global.css in your root index file, the issue is likely caused by too many Providers in your main App component.

The Problem

Metro’s Fast Refresh can’t hot reload components that have too many context providers, state management wrappers, or complex component trees. This is a Metro limitation, not a Uniwind issue.

Common scenario:

App.tsx
import './global.css' // ⚠️ This file has many providers below

export default function App() {
  return (
    <ReduxProvider store={store}>
      <ApolloProvider client={client}>
        <ThemeProvider>
          <AuthProvider>
            <NavigationProvider>
              <NotificationProvider>
                <AnalyticsProvider>
                  {/* Your app */}
                </AnalyticsProvider>
              </NotificationProvider>
            </NavigationProvider>
          </AuthProvider>
        </ThemeProvider>
      </ApolloProvider>
    </ReduxProvider>
  )
}
Metro can’t efficiently hot reload this file due to the complex provider tree, so any change to global.css triggers a full app reload.

The Solution

Move the global.css import one level deeper to a component that has fewer providers:

Option 1: Import in your navigation root

App.tsx
// Remove the import from here
// import './global.css'

export default function App() {
  return (
    <ReduxProvider store={store}>
      <ApolloProvider client={client}>
        <NavigationRoot /> {/* Import CSS here instead */}
      </ApolloProvider>
    </ReduxProvider>
  )
}
NavigationRoot.tsx
import './global.css' // ✅ Import here

export const NavigationRoot = () => {
  return (
    <NavigationContainer>
      {/* Your navigation */}
    </NavigationContainer>
  )
}

Option 2: Import in your home/main screen

screens/HomeScreen.tsx
import '../global.css' // ✅ Import in a screen component

export const HomeScreen = () => {
  return (
    <View className="flex-1 bg-background">
      {/* Your content */}
    </View>
  )
}

Option 3: Import in Expo Router’s nested layout

If using Expo Router, move the import to a nested layout:
app/_layout.tsx
// Remove from root layout
// import './global.css'

export default function RootLayout() {
  return (
    <Providers>
      <Slot />
    </Providers>
  )
}
app/(tabs)/_layout.tsx
import '../global.css' // ✅ Import in nested layout

export default function TabsLayout() {
  return <Tabs />
}

How to Choose Where to Import

Import global.css in the deepest component that:
  1. ✅ Is mounted early in your app lifecycle
  2. ✅ Doesn’t have many providers or complex state
  3. ✅ Is a good candidate for Fast Refresh
  4. ✅ Runs on all platforms (iOS, Android, Web)
The goal is to find a component that Metro can efficiently hot reload. Experiment with different locations until you find one that enables Fast Refresh for CSS changes.

Testing the Fix

After moving the import:
  1. Restart Metro - Clear cache with npx expo start --clear
  2. Make a CSS change - Modify a color in global.css
  3. Check for Fast Refresh - Your app should update without a full reload
If you still see full reloads, try moving the import one level deeper. Some apps with very complex structures may need the import quite deep in the component tree.

Why This Happens

Metro’s Fast Refresh works by:
  1. Detecting which files changed
  2. Finding components that can be safely updated
  3. Hot swapping only those components
When a file has too many providers or complex state management, Metro can’t determine what’s safe to update, so it triggers a full reload instead.
This is a Metro/React Native limitation, not specific to Uniwind. Any file with complex provider trees will have this issue with Fast Refresh.

Fast Refresh Documentation

Learn more about React Native’s Fast Refresh system
Uniwind provides built-in gradient support using Tailwind syntax with React Native’s internal implementation. No additional dependencies required!Use gradient classes directly with the className prop:

Directional Gradients

import { View } from 'react-native'

// Left to right gradient
<View className="bg-gradient-to-r from-indigo-500 to-pink-500 rounded size-16" />

// Top to bottom gradient
<View className="bg-gradient-to-b from-blue-500 to-purple-500 rounded size-16" />

// Diagonal gradient (top-left to bottom-right)
<View className="bg-gradient-to-br from-green-400 to-blue-500 rounded size-16" />
Available directions:
  • bg-gradient-to-t - Top
  • bg-gradient-to-r - Right
  • bg-gradient-to-b - Bottom
  • bg-gradient-to-l - Left
  • bg-gradient-to-tr - Top right
  • bg-gradient-to-br - Bottom right
  • bg-gradient-to-bl - Bottom left
  • bg-gradient-to-tl - Top left

Angle-based Gradients

Use specific angles with bg-linear-{angle}:
import { View } from 'react-native'

// 90 degree gradient
<View className="bg-linear-90 from-indigo-500 via-sky-500 to-pink-500 rounded size-16" />

// 45 degree gradient
<View className="bg-linear-45 from-red-500 to-yellow-500 rounded size-16" />

// 180 degree gradient
<View className="bg-linear-180 from-purple-500 to-pink-500 rounded size-16" />

Multi-stop Gradients

Use from-, via-, and to- for multiple color stops:
import { View } from 'react-native'

// Three color stops
<View className="bg-gradient-to-r from-red-500 via-yellow-500 to-green-500 rounded size-16" />

// With multiple via colors
<View className="bg-linear-90 from-indigo-500 via-sky-500 via-purple-500 to-pink-500 rounded size-16" />

Custom Gradients with Arbitrary Values

For complete control, use arbitrary values with custom angles and color stops:
import { View } from 'react-native'

// Custom angle and color stops with percentages
<View className="bg-linear-[25deg,red_5%,yellow_60%,lime_90%,teal] rounded size-16" />

// Complex gradient
<View className="bg-linear-[135deg,rgba(255,0,0,0.8)_0%,rgba(0,255,0,0.6)_50%,rgba(0,0,255,0.8)_100%] rounded size-16" />
Syntax: bg-linear-[angle,color1_position,color2_position,...]
Built-in gradients work seamlessly with theme colors and support all Tailwind color utilities like from-blue-500, via-purple-600, etc.You can check more examples in the offical Tailwind CSS documentation.

Using expo-linear-gradient

If you need to use expo-linear-gradient for specific features, you can’t use withUniwind since it doesn’t support mapping props to arrays. Instead, use multiple useCSSVariable calls:

❌ This won’t work

import { LinearGradient } from 'expo-linear-gradient'
import { withUniwind } from 'uniwind'

// Can't map className to colors array
const StyledLinearGradient = withUniwind(LinearGradient)

<StyledLinearGradient
  colorsClassName={['accent-red-500', 'accent-transparent']} // ?? Can't map to colors array
/>

✅ Use useCSSVariable instead

import { LinearGradient } from 'expo-linear-gradient'
import { useCSSVariable } from 'uniwind'

export const GradientComponent = () => {
  const startColor = useCSSVariable('--color-indigo-500')
  const midColor = useCSSVariable('--color-pink-200')
  const endColor = useCSSVariable('--color-pink-500')

  return (
    <LinearGradient colors={[startColor, midColor, endColor]}/>
  )
}
For most use cases, we recommend using built-in gradient support instead of expo-linear-gradient. It’s simpler, requires no extra dependencies, and integrates better with Tailwind syntax.

Examples

Card with Gradient Background

import { View, Text } from 'react-native'

export const GradientCard = () => (
  <View className="bg-gradient-to-br from-purple-600 via-pink-500 to-red-500 p-6 rounded-2xl">
    <Text className="text-white text-2xl font-bold">
      Beautiful Gradient Card
    </Text>
    <Text className="text-white/80 mt-2">
      Using built-in gradient support
    </Text>
  </View>
)

Button with Gradient

import { Pressable, Text } from 'react-native'

export const GradientButton = ({ onPress, title }) => (
  <Pressable
    onPress={onPress}
    className="bg-gradient-to-r from-blue-500 to-cyan-500 px-6 py-3 rounded-full active:opacity-80"
  >
    <Text className="text-white text-center font-semibold">
      {title}
    </Text>
  </Pressable>
)

Theme-aware Gradient

import { View, Text } from 'react-native'

export const ThemedGradient = () => (
  <View className="bg-gradient-to-r from-blue-500 to-purple-500 dark:from-blue-700 dark:to-purple-700 p-6 rounded-xl">
    <Text className="text-white text-lg">
      This gradient adapts to the theme
    </Text>
  </View>
)
Uniwind does not automatically deduplicate classNames, especially on web. When you have conflicting styles or duplicate classes, you’ll need to handle merging manually.
Important: Uniwind doesn’t dedupe classNames. If you pass conflicting styles like className="bg-red-500 bg-blue-500", both classes will be applied, and the behavior depends on CSS specificity rules.
For proper className merging and deduplication, we recommend using tailwind-merge with a utility function:

Step 1: Install dependencies

bun add tailwind-merge clsx

Step 2: Create a cn utility

Create a utility file (e.g., lib/utils.ts or utils/cn.ts):
lib/utils.ts
import { type ClassValue, clsx } from 'clsx';
import { twMerge } from 'tailwind-merge';

export function cn(...inputs: ClassValue[]) {
  return twMerge(clsx(inputs));
}

Step 3: Use the cn utility

Now you can merge classNames safely in your components:
import { View, Text, Pressable } from 'react-native'
import { cn } from '@/lib/utils'

export const Button = ({ className, variant = 'default', ...props }) => {
  return (
    <Pressable
      className={cn(
        // Base styles
        'px-4 py-2 rounded-lg',
        // Variant styles
        variant === 'default' && 'bg-blue-500',
        variant === 'destructive' && 'bg-red-500',
        // Custom className (will override conflicting classes)
        className
      )}
      {...props}
    />
  )
}

// Usage
<Button variant="default" className="bg-green-500" />
// Result: bg-green-500 wins, bg-blue-500 is removed

Why Use tailwind-merge?

Without tailwind-merge, conflicting classes can cause issues:

❌ Without tailwind-merge

import { View } from 'react-native'

const baseClasses = 'bg-red-500 p-4'
const customClasses = 'bg-blue-500'

// Both bg-red-500 and bg-blue-500 are applied
// Result is unpredictable
<View className={`${baseClasses} ${customClasses}`} />

✅ With tailwind-merge

import { View } from 'react-native'
import { cn } from '@/lib/utils'

const baseClasses = 'bg-red-500 p-4'
const customClasses = 'bg-blue-500'

// tailwind-merge removes bg-red-500, keeps bg-blue-500
// Result: clean, predictable styling
<View className={cn(baseClasses, customClasses)} />

Conditional Class Merging

The clsx library inside cn makes conditional classes easier:
import { View } from 'react-native'
import { cn } from '@/lib/utils'

export const Card = ({ isActive, isDisabled, className }) => (
  <View
    className={cn(
      'p-4 rounded-lg border',
      isActive && 'border-blue-500 bg-blue-50',
      isDisabled && 'opacity-50',
      !isDisabled && 'active:scale-95',
      className
    )}
  />
)
Understanding style specificity and priority is important when working with Uniwind to ensure predictable styling behavior.

Inline styles override className

In Uniwind inline styles always have higher priority than className:
import { View } from 'react-native'

// Inline style takes priority
<View
  className="bg-red-500"
  style={{ backgroundColor: 'blue' }} // This wins
/>
Result: The background will be blue, not red.
Inline styles always have higher priority than className. If you need to override a className style, you can use inline styles or merge classNames properly with cn from tailwind-merge.

Platform-specific behavior

Specificity rules work consistently across platforms:
import { View } from 'react-native'

<View
  className="bg-red-500 ios:bg-blue-500 android:bg-green-500"
  style={{ backgroundColor: 'purple' }}
/>
Result on all platforms: Purple background (inline style always wins)

Best practices

Use className for static styles and inline styles only for truly dynamic values that can’t be represented as classes (e.g., values from API, animation interpolations).
Use cn from tailwind-merge when building component libraries to ensure predictable className overrides.
Avoid mixing className and inline styles for the same property. Choose one approach for consistency and maintainability.
If you encounter the error “Uniwind Error - Failed to serialize javascript object”, this means Uniwind’s Metro transformer is unable to serialize a complex pattern in your global.css file. This error is specifically about CSS processing, not about classNames in your components.

The Error

Uniwind Error - Failed to serialize javascript object
This error appears during the Metro bundling process when Uniwind tries to process your global.css file. It can cause your app to fail to build or display a white screen.
This error is about CSS patterns in global.css (like complex @theme configurations, custom properties, or advanced CSS features), not about using className in your components.

Debugging Steps

To identify what’s causing the serialization issue, follow these steps:

Step 1: Add debug logging

Navigate to the Uniwind Metro transformer file and add a console log to see what’s failing:
node_modules/uniwind/dist/metro/metro-transformer.cjs
// Find the serializeJSObject function and update the catch block:

try {
  new Function(`function validateJS() { const obj = ({ ${serializedObject} }) }`);
} catch {
  // Add this console.log to see what's failing
  console.log('Serialization failed for:', serializedObject); 
  Logger.error("Failed to serialize javascript object");
  return "";
}
return serializedObject;

Step 2: Run your app

After adding the console log, run your Metro bundler:
npx expo start --clear
# or
npx react-native start --reset-cache

Step 3: Check the output

Look at your Metro terminal output. You should see which object or code pattern is causing the serialization failure.

Step 4: Report the issue

Once you’ve identified the problematic code:
  1. Copy the console.log output
  2. Create a minimal reproduction case if possible
  3. Report it on GitHub with the output
Include the serialization output and the code pattern causing the issue. This helps the maintainers fix the serializer to support your use case.

Common Causes in global.css

This error is caused by complex patterns in your global.css file that the Metro transformer can’t serialize. Common causes include:
  • Complex @theme configurations - Very large or deeply nested theme definitions
  • Advanced CSS functions - Custom CSS functions or calculations that use JavaScript-like syntax
  • Non-standard CSS syntax - Experimental or non-standard CSS features
  • Circular references - CSS variables that reference each other in complex ways

Temporary Workarounds

While waiting for a fix:
  • Simplify your global.css - Break down complex theme configurations into smaller, simpler parts
  • Remove experimental features - Comment out advanced CSS features to isolate the issue
Modifying files in node_modules is only for debugging. Your changes will be lost when you reinstall dependencies. Always report the issue on GitHub for a permanent fix.

Report Serialization Issues

Found a serialization issue? Help improve Uniwind by reporting it
Some React Native apps (especially crypto apps) need to disable unstable_enablePackageExports in their Metro configuration. However, Uniwind requires this setting to be enabled to work properly.

The Problem

If your Metro config has:
metro.config.js
config.resolver.unstable_enablePackageExports = false
Uniwind and its dependency (culori) won’t work correctly because they require package exports to be enabled.
Completely disabling unstable_enablePackageExports will break Uniwind’s module resolution.

The Solution

You can selectively enable package exports only for Uniwind and its dependencies while keeping it disabled for everything else:
metro.config.js
const { getDefaultConfig } = require('expo/metro-config');
const { withUniwindConfig } = require('uniwind/metro');

const config = getDefaultConfig(__dirname);

// Disable package exports globally (for crypto libraries, etc.)
config.resolver.unstable_enablePackageExports = false;

// Selectively enable package exports for Uniwind and culori
config.resolver.resolveRequest = (context, moduleName, platform) => {
  // uniwind and its dependency (culori) require unstable_enablePackageExports to be true
  if (['uniwind', 'culori'].some((prefix) => moduleName.startsWith(prefix))) {
    const newContext = {
      ...context,
      unstable_enablePackageExports: true,
    };

    return context.resolveRequest(newContext, moduleName, platform);
  }

  // default behavior for everything else
  return context.resolveRequest(context, moduleName, platform);
};

module.exports = withUniwindConfig(config, {
  cssEntryFile: './src/global.css',
});
This custom resolver enables package exports only when resolving uniwind and culori, while keeping it disabled for all other packages.

Why This Works

The custom resolveRequest function:
  1. Checks the module name - If it’s uniwind or culori, it enables package exports
  2. Creates a new context - Temporarily overrides the setting for these specific packages
  3. Falls back to default - All other packages use the global setting (false)

When You Need This

Use this solution if:
  • You’re working with crypto libraries that break with package exports enabled
  • You have other dependencies that require unstable_enablePackageExports = false
  • You encounter module resolution errors with Uniwind after disabling package exports
If you don’t have any conflicts with unstable_enablePackageExports, you don’t need this custom resolver. Uniwind works fine with the default Metro configuration.

Troubleshooting

If you still encounter issues after adding the custom resolver:
  1. Clear Metro cache - Run npx expo start --clear or npx react-native start --reset-cache
  2. Rebuild the app - Package export changes may require a full rebuild
  3. Check the module name - Ensure the module causing issues is included in the ['uniwind', 'culori'] array
  4. Verify Metro config - Make sure the custom resolver is defined before calling withUniwindConfig

Metro Configuration

Learn more about configuring Metro for Uniwind
Available in Uniwind 1.2.0+Yes. Use Vite with React Native Web, Tailwind, and the Uniwind Vite plugin.

Setup

Create vite.config.ts in your project root:
vite.config.ts
import tailwindcss from '@tailwindcss/vite'
import { uniwind } from 'uniwind/vite'
import { defineConfig } from 'vite'
import { rnw } from 'vite-plugin-rnw'

export default defineConfig({
    plugins: [
        rnw(),
        tailwindcss(),
        uniwind({
            // support same configuration as Metro plugin
            cssEntryFile: './src/App.css',
            extraThemes: ['premium'],
        }),
    ],
})
Point cssEntryFile to the CSS file where you import tailwindcss and uniwind. Keep it at your app root for accurate class scanning.
Restart Vite after changing your CSS entry or adding new themes.
Storybook works via Vite too! Use the same uniwind/vite plugin in your Storybook Vite config and follow the React Native Web + Vite guide: https://storybook.js.org/docs/get-started/frameworks/react-native-web-vite. This gives you the web Storybook UI for visual and interaction testing.
Available in Uniwind 1.2.0+Install react-native-safe-area-context and wire safe area insets to Uniwind.
This applies only to the open source version of Uniwind. In the Pro version, insets are injected automatically from C++.

Setup

  1. Add the dependency:
bun add react-native-safe-area-context
  1. Wrap your root layout with SafeAreaListener and forward insets to Uniwind:
import { SafeAreaListener } from 'react-native-safe-area-context'
import { Uniwind } from 'uniwind'

export const Root = () => (
  <SafeAreaListener
    onChange={({ insets }) => {
      Uniwind.updateInsets(insets)
    }}
  >
    {/* app content */}
  </SafeAreaListener>
)
Add the listener once at the root of your app to keep all screens in sync.

Available classNames

Uniwind provides three categories of safe area utilities:
  • Padding: p-safe, pt-safe, pb-safe, pl-safe, pr-safe, px-safe, py-safe
  • Margin: m-safe, mt-safe, mb-safe, ml-safe, mr-safe, mx-safe, my-safe
  • Inset (positioning): inset-safe, top-safe, bottom-safe, left-safe, right-safe, x-safe, y-safe
Each utility also supports or and offset variants:
  • {property}-safe-or-{value}Math.max(inset, value) - ensures minimum spacing (e.g., pt-safe-or-4)
  • {property}-safe-offset-{value}inset + value - adds extra spacing on top of inset (e.g., mb-safe-offset-2)

Class matrix

ClassExampleEffect
p-safeclassName="p-safe"Sets all padding to the current inset values
pt-safeclassName="pt-safe"Top padding equals top inset
m-safeclassName="m-safe"Sets all margins to the inset values
inset-safeclassName="inset-safe"Sets top/bottom/left/right position to inset values
top-safeclassName="top-safe"Top position equals top inset
y-safeclassName="y-safe"Top and bottom positions equal their insets
pt-safe-or-4className="pt-safe-or-4"Top padding is Math.max(topInset, 16)
pb-safe-offset-4className="pb-safe-offset-4"Bottom padding is bottomInset + 16
top-safe-offset-4className="top-safe-offset-4"Top position is topInset + 16

Positioning Examples

Use inset utilities for absolutely positioned elements that need to respect safe areas:
// Floating action button above the bottom safe area
<Pressable className="absolute bottom-safe right-4 bg-blue-500 rounded-full p-4">
  <PlusIcon />
</Pressable>

// Full-screen overlay respecting all safe areas
<View className="absolute inset-safe bg-black/50">
  {/* Modal content */}
</View>

// Header positioned below top safe area with extra padding
<View className="absolute top-safe-offset-4 left-0 right-0">
  {/* Header content */}
</View>
Not officially. Uniwind is built for Metro and Vite (via React Native Web), not for Next.js. However, there’s an experimental community-driven plugin that adds Next.js support.

Current Support

Uniwind works out of the box with:
  • React Native (Bare workflow)
  • Expo (Managed and bare workflows)
  • Metro bundler (React Native’s default bundler)
  • Vite (with vite-plugin-rnw and uniwind/vite for web)

Why Not Next.js?

Next.js uses Webpack (or Turbopack) as its bundler, while Uniwind is architected around Metro’s transformer pipeline. These are fundamentally different build systems with different APIs and plugin architectures.

Community Solution

Experimental - This is a community-driven package, not officially maintained by the Uniwind team.
@a16n-dev has created uniwind-plugin-next, a webpack plugin that integrates Uniwind into Next.js applications with SSR support.

Official Next.js Support

There is currently no timeline for official Next.js support. While the community plugin works well for many use cases, official support would require significant effort to build and maintain a separate Webpack/Turbopack plugin alongside the Metro architecture.If the community plugin doesn’t meet your needs, consider the alternatives below.

Alternatives for Cross-Platform

If the community plugin doesn’t fit your needs:
  • Use Uniwind for React Native/Expo - For your mobile apps
  • Use standard Tailwind CSS for Next.js - For your web app
  • Share design tokens - Keep your color palette and spacing consistent via shared configuration
Many teams successfully use Uniwind for their React Native apps while using standard Tailwind CSS for their Next.js web apps, sharing design tokens between them.
Uniwind works with any React Native component library, but we’ve worked closely with UI kit teams to ensure the best integration and performance.

React Native Reusables - shadcn for React Native

React Native Reusables brings the beloved shadcn/ui philosophy to React Native. Built with Uniwind (or NativeWind), it provides beautifully designed, accessible, and customizable components that you can copy and paste into your apps.Why React Native Reusables?
  • 🎨 shadcn Philosophy - Copy, paste, and own your components. No package bloat
  • Uniwind Native - Built specifically for Uniwind with full className support
  • 🎯 Beautifully Crafted - Premium design inspired by shadcn/ui’s aesthetics
  • Accessible - WCAG-compliant components that work across all platforms
  • 🎛️ Fully Customizable - Modify components to match your exact design requirements
  • 📱 React Native First - Designed for mobile, works perfectly on iOS, Android, and web
Perfect for developers who love shadcn/ui’s approach and want the same elegant components for React Native. Just copy, paste, and customize to your heart’s content!

HeroUI Native - Complete Component Library

HeroUI Native is a comprehensive, production-ready React Native UI library. It’s built for speed, accessibility, and seamless integration with Uniwind.Why HeroUI Native?
  • Built for Uniwind - Designed to work seamlessly with Uniwind’s styling system
  • Optimized Performance - Collaborated closely with the HeroUI team for best-in-class performance
  • Accessible - ARIA-compliant components that work on all platforms
  • Extensive Theming - Deep integration with Uniwind’s theme system
  • Modern Design - Beautiful, contemporary components out of the box
  • Comprehensive - Full set of components for building production apps
Both UI kits work seamlessly with Uniwind’s className prop and support all Tailwind utilities out of the box. Choose based on your preferred workflow: copy-paste (Reusables) or npm install (HeroUI).

More UI Kits Coming

We’re actively working with other UI library teams to bring first-class Uniwind support to more component libraries. Stay tuned for announcements!
Want your UI kit featured here? We collaborate closely with library authors to ensure optimal integration and performance. Reach out on GitHub Discussions!

Using Other Component Libraries

Uniwind works with any React Native component library. For libraries that don’t natively support className, you can use withUniwind to add className support:
import { withUniwind } from 'uniwind'
import { SomeComponent } from 'some-library'

const StyledComponent = withUniwind(SomeComponent)

<StyledComponent className="bg-blue-500 p-4" />

withUniwind API

Learn how to add className support to any component

Third-party Components

See examples of using Uniwind with various component libraries