Skill v1.0.0
currentTrusted Publisher100/100name: 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
# Install dependenciesnpx 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:
// package.json{"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:
// metro.config.jsconst { getDefaultConfig } = require("expo/metro-config");const { withNativewind } = require("nativewind/metro");/** @type {import('expo/metro-config').MetroConfig} */const config = getDefaultConfig(__dirname);module.exports = withNativewind(config, {// inline variables break PlatformColor in CSS variablesinlineVariables: false,// We add className support manuallyglobalClassNamePolyfill: false,});
PostCSS Config
Create postcss.config.mjs:
// postcss.config.mjsexport 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";/* Platform-specific font families */@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:
// DELETE babel.config.js if it only contains NativeWind config// The following is NO LONGER needed:// module.exports = function (api) {// api.cache(true);// return {// presets: [// ["babel-preset-expo", { jsxImportSource: "nativewind" }],// "nativewind/babel",// ],// };// };
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 { Link as RouterLink } from "expo-router";import Animated from "react-native-reanimated";import React from "react";import {View as RNView,Text as RNText,Pressable as RNPressable,ScrollView as RNScrollView,TouchableHighlight as RNTouchableHighlight,TextInput as RNTextInput,StyleSheet,} from "react-native";// CSS-enabled Linkexport const Link = (props: React.ComponentProps<typeof RouterLink> & { className?: string }) => {return useCssElement(RouterLink, props, { className: "style" });};Link.Trigger = RouterLink.Trigger;Link.Menu = RouterLink.Menu;Link.MenuAction = RouterLink.MenuAction;Link.Preview = RouterLink.Preview;// CSS Variable hookexport const useCSSVariable =process.env.EXPO_OS !== "web"? useFunctionalVariable: (variable: string) => `var(${variable})`;// Viewexport type ViewProps = React.ComponentProps<typeof RNView> & {className?: string;};export const View = (props: ViewProps) => {return useCssElement(RNView, props, { className: "style" });};View.displayName = "CSS(View)";// Textexport const Text = (props: React.ComponentProps<typeof RNText> & { className?: string }) => {return useCssElement(RNText, props, { className: "style" });};Text.displayName = "CSS(Text)";// ScrollViewexport const ScrollView = (props: React.ComponentProps<typeof RNScrollView> & {className?: string;contentContainerClassName?: string;}) => {return useCssElement(RNScrollView, props, {className: "style",contentContainerClassName: "contentContainerStyle",});};ScrollView.displayName = "CSS(ScrollView)";// Pressableexport const Pressable = (props: React.ComponentProps<typeof RNPressable> & { className?: string }) => {return useCssElement(RNPressable, props, { className: "style" });};Pressable.displayName = "CSS(Pressable)";// TextInputexport const TextInput = (props: React.ComponentProps<typeof RNTextInput> & { className?: string }) => {return useCssElement(RNTextInput, props, { className: "style" });};TextInput.displayName = "CSS(TextInput)";// AnimatedScrollViewexport const AnimatedScrollView = (props: React.ComponentProps<typeof Animated.ScrollView> & {className?: string;contentClassName?: string;contentContainerClassName?: string;}) => {return useCssElement(Animated.ScrollView, props, {className: "style",contentClassName: "contentContainerStyle",contentContainerClassName: "contentContainerStyle",});};// TouchableHighlight with underlayColor extractionfunction XXTouchableHighlight(props: React.ComponentProps<typeof RNTouchableHighlight>) {const { underlayColor, ...style } = StyleSheet.flatten(props.style) || {};return (<RNTouchableHighlightunderlayColor={underlayColor}{...props}style={style}/>);}export const TouchableHighlight = (props: React.ComponentProps<typeof RNTouchableHighlight>) => {return useCssElement(XXTouchableHighlight, props, { className: "style" });};TouchableHighlight.displayName = "CSS(TouchableHighlight)";
Image Component (src/tw/image.tsx)
import { useCssElement } from "react-native-css";import React from "react";import { StyleSheet } from "react-native";import Animated from "react-native-reanimated";import { Image as RNImage } from "expo-image";const AnimatedExpoImage = Animated.createAnimatedComponent(RNImage);export type ImageProps = React.ComponentProps<typeof Image>;function CSSImage(props: React.ComponentProps<typeof AnimatedExpoImage>) {// @ts-expect-error: Remap objectFit style to contentFit propertyconst { objectFit, objectPosition, ...style } =StyleSheet.flatten(props.style) || {};return (<AnimatedExpoImagecontentFit={objectFit}contentPosition={objectPosition}{...props}source={typeof props.source === "string" ? { uri: props.source } : props.source}// @ts-expect-error: Style is remapped abovestyle={style}/>);}export const Image = (props: React.ComponentProps<typeof CSSImage> & { className?: string }) => {return useCssElement(CSSImage, props, { className: "style" });};Image.displayName = "CSS(Image)";
Animated Components (src/tw/animated.tsx)
import * as TW from "./index";import RNAnimated from "react-native-reanimated";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><ImageclassName="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 {/* Custom fonts */--font-rounded: "SF Pro Rounded", sans-serif;/* Custom line heights */--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);/* Custom leading scales */--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:
/* src/css/sf.css */@layer base {html {color-scheme: light;}}:root {/* Accent colors with light/dark mode */--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));/* Gray scales */--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));/* Text colors */--sf-text: light-dark(rgb(0 0 0), rgb(255 255 255));--sf-text-2: light-dark(rgb(60 60 67 / 0.6), rgb(235 235 245 / 0.6));/* Background colors */--sf-bg: light-dark(rgb(255 255 255), rgb(0 0 0));--sf-bg-2: light-dark(rgb(242 242 247), rgb(28 28 30));}/* iOS native colors via platformColor */@media ios {:root {--sf-blue: platformColor(systemBlue);--sf-green: platformColor(systemGreen);--sf-red: platformColor(systemRed);--sf-gray: platformColor(systemGray);--sf-text: platformColor(label);--sf-text-2: platformColor(secondaryLabel);--sf-bg: platformColor(systemBackground);--sf-bg-2: platformColor(secondarySystemBackground);}}/* Register as Tailwind theme colors */@layer theme {@theme {--color-sf-blue: var(--sf-blue);--color-sf-green: var(--sf-green);--color-sf-red: var(--sf-red);--color-sf-gray: var(--sf-gray);--color-sf-text: var(--sf-text);--color-sf-text-2: var(--sf-text-2);--color-sf-bg: var(--sf-bg);--color-sf-bg-2: var(--sf-bg-2);}}
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/postcssinstead oftailwindcss - CSS imports - Use
@import "tailwindcss/..."instead of@tailwinddirectives - Theme config - Use
@themein CSS instead oftailwind.config.js - Component wrappers - Must wrap components with
useCssElementfor className support - Metro config - Use
withNativewindwith 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
withNativewindapplied
Platform colors not working
- Use
platformColor()in@media iosblocks - Fall back to
light-dark()for web/Android
TypeScript errors
Add className to component props:
type Props = React.ComponentProps<typeof RNView> & { className?: string };