| name | expo-tailwind-setup |
| description | Set up Tailwind CSS v4 in Expo with react-native-css and NativeWind v5 for universal styling |
| version | 1.0.0 |
| license | MIT |
Tailwind CSS Setup for Expo with react-native-css
This guide covers setting up Tailwind CSS v4 in Expo using react-native-css and NativeWind v5 for universal styling across iOS, Android, and Web.
Overview
This setup uses:
- Tailwind CSS v4 - Modern CSS-first configuration
- react-native-css - CSS runtime for React Native
- NativeWind v5 - Metro transformer for Tailwind in React Native
- @tailwindcss/postcss - PostCSS plugin for Tailwind v4
Installation
npx expo install tailwindcss@^4 nativewind@5.0.0-preview.2 react-native-css@0.0.0-nightly.5ce6396 @tailwindcss/postcss tailwind-merge clsx
Add resolutions for lightningcss compatibility:
{
"resolutions": {
"lightningcss": "1.30.1"
}
}
- autoprefixer is not needed in Expo because of lightningcss
- postcss is included in expo by default
Configuration Files
Metro Config
Create or update metro.config.js:
const {getDefaultConfig} = require('expo/metro-config')
const {withNativewind} = require('nativewind/metro')
const config = getDefaultConfig(__dirname)
module.exports = withNativewind(config, {
inlineVariables: false,
globalClassNamePolyfill: false,
})
PostCSS Config
Create postcss.config.mjs:
export default {
plugins: {
'@tailwindcss/postcss': {},
},
}
Global CSS
Create src/global.css:
@import 'tailwindcss/theme.css' layer(theme);
@import 'tailwindcss/preflight.css' layer(base);
@import 'tailwindcss/utilities.css';
@media android {
:root {
--font-mono: monospace;
--font-rounded: normal;
--font-serif: serif;
--font-sans: normal;
}
}
@media ios {
:root {
--font-mono: ui-monospace;
--font-serif: ui-serif;
--font-sans: system-ui;
--font-rounded: ui-rounded;
}
}
IMPORTANT: No Babel Config Needed
With Tailwind v4 and NativeWind v5, you do NOT need a babel.config.js for Tailwind. Remove any NativeWind babel presets if present:
CSS Component Wrappers
Since react-native-css requires explicit CSS element wrapping, create reusable components:
Main Components (src/tw/index.tsx)
import {
useCssElement,
useNativeVariable as useFunctionalVariable,
} from 'react-native-css'
import {
View as RNView,
Text as RNText,
Pressable as RNPressable,
ScrollView as RNScrollView,
TouchableHighlight as RNTouchableHighlight,
TextInput as RNTextInput,
StyleSheet,
} from 'react-native'
import Animated from 'react-native-reanimated'
import {Link as RouterLink} from 'expo-router'
import React from 'react'
export const Link = (
props: React.ComponentProps<typeof RouterLink> & {className?: string},
) => {
return useCssElement(, props, {: })
}
. = .
. = .
. = .
. = .
useCSSVariable =
process.. !==
? useFunctionalVariable
:
= .< > & {
?:
}
= () => {
(, props, {: })
}
. =
= () => {
(, props, {: })
}
. =
= () => {
(, props, {
: ,
: ,
})
}
. =
= () => {
(, props, {: })
}
. =
= () => {
(, props, {: })
}
. =
= () => {
(., props, {
: ,
: ,
: ,
})
}
() {
{underlayColor, ...style} = .(props.) || {}
(
)
}
= () => {
(, props, {: })
}
. =
Image Component (src/tw/image.tsx)
import Animated from 'react-native-reanimated'
import {useCssElement} from 'react-native-css'
import {Image as RNImage} from 'expo-image'
import {StyleSheet} from 'react-native'
import React from 'react'
const AnimatedExpoImage = Animated.createAnimatedComponent(RNImage)
export type ImageProps = React.ComponentProps<typeof Image>
function CSSImage(props: React.ComponentProps<typeof AnimatedExpoImage>) {
const {objectFit, objectPosition, ...style} =
StyleSheet.flatten(props.style) || {}
return (
<AnimatedExpoImage
contentFit={objectFit}
=
{}
=
=== ? { }
}
// @
=
/>
)
}
= () => {
(, props, {: })
}
. =
Animated Components (src/tw/animated.tsx)
import RNAnimated from 'react-native-reanimated'
import * as TW from './index'
export const Animated = {
...RNAnimated,
View: RNAnimated.createAnimatedComponent(TW.View),
}
Usage
Import CSS-wrapped components from your tw directory:
import {View, Text, ScrollView, Image} from '@/tw'
export default function MyScreen() {
return (
<ScrollView className="flex-1 bg-white">
<View className="p-4 gap-4">
<Text className="text-xl font-bold text-gray-900">Hello Tailwind!</Text>
<Image
className="w-full h-48 rounded-lg object-cover"
source={{uri: 'https://example.com/image.jpg'}}
/>
</View>
</ScrollView>
)
}
Custom Theme Variables
Add custom theme variables in your global.css using @theme:
@layer theme {
@theme {
--font-rounded: 'SF Pro Rounded', sans-serif;
--text-xs--line-height: calc(1em / 0.75);
--text-sm--line-height: calc(1.25em / 0.875);
--text-base--line-height: calc(1.5em / 1);
--leading-tight: 1.25em;
--leading-snug: 1.375em;
--leading-normal: 1.5em;
}
}
Platform-Specific Styles
Use platform media queries for platform-specific styling:
@media ios {
:root {
--font-sans: system-ui;
--font-rounded: ui-rounded;
}
}
@media android {
:root {
--font-sans: normal;
--font-rounded: normal;
}
}
Apple System Colors with CSS Variables
Create a CSS file for Apple semantic colors:
@layer base {
html {
color-scheme: light;
}
}
:root {
--sf-blue: light-dark(rgb(0 122 255), rgb(10 132 255));
--sf-green: light-dark(rgb(52 199 89), rgb(48 209 89));
--sf-red: light-dark(rgb(255 59 48), rgb(255 69 58));
--sf-gray: light-dark(rgb(142 142 147), rgb(142 142 147));
--sf-gray-2: light-dark(rgb(174 174 178), rgb(99 99 102));
: (( ), ( ));
: (( / ), ( / ));
: (( ), ( ));
: (( ), ( ));
}
ios {
{
: (systemBlue);
: (systemGreen);
: (systemRed);
: (systemGray);
: (label);
: (secondaryLabel);
: (systemBackground);
: (secondarySystemBackground);
}
}
theme {
{
: (--sf-blue);
: (--sf-green);
: (--sf-red);
: (--sf-gray);
: (--sf-text);
: (--sf-text-);
: (--sf-bg);
: (--sf-bg-);
}
}
Then use in components:
<Text className="text-sf-text">Primary text</Text>
<Text className="text-sf-text-2">Secondary text</Text>
<View className="bg-sf-bg">...</View>
Using CSS Variables in JavaScript
Use the useCSSVariable hook:
import {useCSSVariable} from '@/tw'
function MyComponent() {
const blue = useCSSVariable('--sf-blue')
return <View style={{borderColor: blue}} />
}
Key Differences from NativeWind v4 / Tailwind v3
- No babel.config.js - Configuration is now CSS-first
- PostCSS plugin - Uses
@tailwindcss/postcss instead of tailwindcss
- CSS imports - Use
@import "tailwindcss/..." instead of @tailwind directives
- Theme config - Use
@theme in CSS instead of tailwind.config.js
- Component wrappers - Must wrap components with
useCssElement for className support
- Metro config - Use
withNativewind with different options (inlineVariables: false)
Troubleshooting
Styles not applying
- Ensure you have the CSS file imported in your app entry
- Check that components are wrapped with
useCssElement
- Verify Metro config has
withNativewind applied
Platform colors not working
- Use
platformColor() in @media ios blocks
- Fall back to
light-dark() for web/Android
TypeScript errors
Add className to component props:
type Props = React.ComponentProps<typeof RNView> & {className?: string}