NativeWind provides first-class support for React Native Web, allowing you to use the same Tailwind CSS styling across native and web platforms. NativeWind automatically uses CSS stylesheets on web for optimal performance.
This guide covers adding web support to React Native apps. For Expo projects, web support is built-in. See the Expo Setup Guide .
How It Works
NativeWind uses different rendering strategies for optimal performance on each platform:
Native Platforms (iOS/Android)
Styles are compiled to React Native’s StyleSheet.create API, providing native performance.
Web Platform
Styles are rendered as CSS stylesheets, leveraging browser optimizations for the best web performance.
Universal API
You write the same className prop code that works across all platforms seamlessly.
Prerequisites
Before adding web support, ensure you have:
Existing React Native project with NativeWind configured
Node.js 20+ installed
webpack or Metro bundler configured
Installation
For Expo Projects
Expo includes web support out of the box:
npx expo install react-dom react-native-web
Then run:
npm run web
# or
expo start --web
Expo projects automatically configure webpack for web. See Expo Setup Guide for full configuration.
For React Native CLI Projects
Install Dependencies
Install React Native Web and related packages: npm install react-native-web react-dom
npm install --save-dev webpack webpack-cli webpack-dev-server
npm install --save-dev babel-loader html-webpack-plugin
npm install --save-dev @babel/preset-react @babel/preset-typescript
Configure webpack
Create webpack.config.js in your project root: const path = require ( 'path' );
const HtmlWebpackPlugin = require ( 'html-webpack-plugin' );
module . exports = {
entry: './index.web.js' ,
output: {
path: path . resolve ( __dirname , 'dist' ),
filename: 'bundle.js' ,
},
mode: 'development' ,
module: {
rules: [
{
test: / \. ( js | jsx | ts | tsx ) $ / ,
exclude: /node_modules/ ,
use: {
loader: 'babel-loader' ,
},
},
{
test: / \. css $ / ,
use: [ 'style-loader' , 'css-loader' , 'postcss-loader' ],
},
],
},
resolve: {
alias: {
'react-native$' : 'react-native-web' ,
},
extensions: [ '.web.js' , '.web.ts' , '.web.tsx' , '.js' , '.ts' , '.tsx' , '.json' ],
},
plugins: [
new HtmlWebpackPlugin ({
template: './public/index.html' ,
}),
],
devServer: {
static: {
directory: path . join ( __dirname , 'public' ),
},
port: 3000 ,
hot: true ,
},
};
Create Web Entry Point
Create index.web.js in your project root: import { AppRegistry } from 'react-native' ;
import App from './App' ;
import './global.css' ;
AppRegistry . registerComponent ( 'YourApp' , () => App );
AppRegistry . runApplication ( 'YourApp' , {
rootTag: document . getElementById ( 'root' ),
});
Create HTML Template
Create public/index.html: <! DOCTYPE html >
< html lang = "en" >
< head >
< meta charset = "utf-8" />
< meta name = "viewport" content = "width=device-width, initial-scale=1" />
< meta name = "theme-color" content = "#000000" />
< title > Your App </ title >
< style >
body {
margin : 0 ;
padding : 0 ;
font-family : -apple-system , BlinkMacSystemFont, 'Segoe UI' , 'Roboto' , 'Oxygen' ,
'Ubuntu' , 'Cantarell' , 'Fira Sans' , 'Droid Sans' , 'Helvetica Neue' ,
sans-serif ;
-webkit-font-smoothing : antialiased ;
-moz-osx-font-smoothing : grayscale ;
}
#root {
display : flex ;
height : 100 vh ;
width : 100 vw ;
}
</ style >
</ head >
< body >
< noscript > You need to enable JavaScript to run this app. </ noscript >
< div id = "root" ></ div >
</ body >
</ html >
Update package.json Scripts
Add web scripts to package.json: {
"scripts" : {
"web" : "webpack serve --mode development" ,
"build:web" : "webpack --mode production"
}
}
Configuration
PostCSS Configuration
Ensure postcss.config.mjs is properly configured:
export default {
plugins: {
'@tailwindcss/postcss' : {},
} ,
} ;
CSS Loaders for webpack
Install CSS loaders:
npm install --save-dev style-loader css-loader postcss-loader
These loaders enable webpack to process CSS files and apply PostCSS transformations.
Web-Specific Styling
NativeWind provides web-specific utilities and behaviors:
Use the web: modifier for web-only styles:
import { View , Text } from 'react-native' ;
function Card () {
return (
< View className = "p-4 web:hover:shadow-xl web:cursor-pointer" >
< Text className = "text-lg web:select-text" >
This card has web-specific interactions
</ Text >
</ View >
);
}
Hover States
Hover states work automatically on web:
import { Pressable , Text } from 'react-native' ;
function Button () {
return (
< Pressable className = "bg-blue-500 hover:bg-blue-600 px-4 py-2 rounded" >
< Text className = "text-white" > Hover me </ Text >
</ Pressable >
);
}
Hover states work on web by default. On native platforms, use active: for press states.
Focus States
import { TextInput } from 'react-native' ;
function Input () {
return (
< TextInput
className = "border border-gray-300 focus:border-blue-500 focus:ring-2 focus:ring-blue-200 px-3 py-2 rounded"
placeholder = "Focus me"
/>
);
}
Cursor Utilities
Web-specific cursor styles:
< View className = "web:cursor-pointer" />
< View className = "web:cursor-not-allowed" />
< View className = "web:cursor-wait" />
Responsive Design
NativeWind’s responsive utilities work seamlessly on web:
function ResponsiveLayout () {
return (
< View className = "flex-col md:flex-row gap-4 p-4" >
< View className = "flex-1 bg-blue-100 p-4 rounded-lg" >
< Text className = "text-sm md:text-base lg:text-lg" >
Responsive text size
</ Text >
</ View >
< View className = "flex-1 bg-green-100 p-4 rounded-lg" >
< Text > Second column </ Text >
</ View >
</ View >
);
}
Breakpoints:
sm: - 640px
md: - 768px
lg: - 1024px
xl: - 1280px
2xl: - 1536px
Web-Specific Components
Some components behave differently on web:
import { ScrollView , View } from 'react-native' ;
function WebOptimizedScroll () {
return (
< ScrollView className = "flex-1 web:overflow-auto" >
< View className = "p-4" >
{ /* Content */ }
</ View >
</ ScrollView >
);
}
Links and Navigation
For web navigation, consider using web-specific routing:
// Using a web router like React Router
import { Pressable , Text } from 'react-native' ;
function NavLink ({ href , children }) {
return (
< Pressable
className = "hover:bg-gray-100 px-4 py-2 rounded"
onPress = { () => {
// Handle navigation
if ( typeof window !== 'undefined' ) {
window . location . href = href ;
}
} }
>
< Text className = "text-blue-600 hover:text-blue-800" > { children } </ Text >
</ Pressable >
);
}
Code Splitting
For better web performance, use code splitting:
module . exports = {
optimization: {
splitChunks: {
chunks: 'all' ,
},
},
};
For production builds, extract CSS:
npm install --save-dev mini-css-extract-plugin
const MiniCssExtractPlugin = require ( 'mini-css-extract-plugin' );
module . exports = {
plugins: [
new MiniCssExtractPlugin ({
filename: '[name].[contenthash].css' ,
}),
],
module: {
rules: [
{
test: / \. css $ / ,
use: [
process . env . NODE_ENV === 'production'
? MiniCssExtractPlugin . loader
: 'style-loader' ,
'css-loader' ,
'postcss-loader' ,
],
},
],
},
};
SEO Considerations
Add proper meta tags in index.html:
< head >
< meta charset = "utf-8" />
< meta name = "viewport" content = "width=device-width, initial-scale=1" />
< meta name = "description" content = "Your app description" />
< meta property = "og:title" content = "Your App" />
< meta property = "og:description" content = "Your app description" />
< title > Your App </ title >
</ head >
Server-Side Rendering (SSR)
For SSR support, consider using frameworks like:
Next.js with react-native-web
Remix with custom React Native Web integration
Troubleshooting
Styles not applying on web
Verify global.css is imported in your web entry point
Check webpack CSS loaders are configured correctly
Ensure PostCSS is processing the CSS file
Clear webpack cache: rm -rf dist node_modules/.cache
Check webpack resolve.alias includes react-native-web
Verify resolve.extensions includes .web.js, .web.ts, .web.tsx
Ensure React Native Web is installed correctly
Use :hover and :focus pseudo-classes (they work automatically on web)
For native platforms, use active: for press states
Verify Pressable components are used instead of View for interactive elements
Dark mode not working on web
Ensure dark: classes are used
Check browser/OS dark mode settings
Implement manual dark mode toggle if needed
Verify CSS media queries are working: @media (prefers-color-scheme: dark)
Advanced Web Features
Custom Fonts on Web
@font-face {
font-family : 'CustomFont' ;
src : url ( './fonts/CustomFont.woff2' ) format ( 'woff2' );
font-weight : normal ;
font-style : normal ;
}
< Text className = "font-[CustomFont]" > Custom font text </ Text >
Web Animations
Use CSS animations on web:
@keyframes slideIn {
from {
transform : translateX ( -100 % );
}
to {
transform : translateX ( 0 );
}
}
< View className = "web:animate-[slideIn_0.3s_ease-out]" >
< Text > Animated content </ Text >
</ View >
Progressive Web App (PWA)
Convert your app to a PWA by adding a manifest:
< link rel = "manifest" href = "/manifest.json" />
{
"name" : "Your App" ,
"short_name" : "App" ,
"start_url" : "/" ,
"display" : "standalone" ,
"background_color" : "#ffffff" ,
"theme_color" : "#000000" ,
"icons" : [
{
"src" : "/icon-192.png" ,
"sizes" : "192x192" ,
"type" : "image/png"
},
{
"src" : "/icon-512.png" ,
"sizes" : "512x512" ,
"type" : "image/png"
}
]
}
Deployment
Static Hosting
Build for production and deploy to static hosting:
Deploy the dist folder to:
Vercel : vercel deploy
Netlify : netlify deploy --prod --dir=dist
GitHub Pages : Use GitHub Actions for automated deployment
AWS S3 : Upload dist folder to S3 bucket
Environment Variables
For different environments:
const webpack = require ( 'webpack' );
module . exports = {
plugins: [
new webpack . DefinePlugin ({
'process.env.API_URL' : JSON . stringify ( process . env . API_URL || 'http://localhost:3000' ),
}),
],
};
Next Steps
Responsive Design Learn responsive design patterns for web and native
Dark Mode Implement dark mode across all platforms
Custom Styles Add custom utilities and styles
Troubleshooting Common issues and solutions