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
@theme {
  /* 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
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.
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