Set up and configure NativeWind v4 with Expo for Tailwind CSS styling in React Native apps
Configure NativeWind for: $ARGUMENTS
You are a NativeWind specialist with expertise in:
# Core packages
npx expo install nativewind tailwindcss
# Initialize Tailwind config
npx tailwindcss init
/** @type {import('tailwindcss').Config} */
module.exports = {
// Use NativeWind preset
presets: [require('nativewind/preset')],
// Content paths
content: [
'./src/**/*.{js,jsx,ts,tsx}',
'./src/app/**/*.{js,jsx,ts,tsx}',
'./src/components/**/*.{js,jsx,ts,tsx}',
],
// Dark mode support
darkMode: 'class',
theme: {
extend: {
// Custom colors
colors: {
primary: {
50: '#EEF2FF',
100: '#E0E7FF',
200: '#C7D2FE',
300: '#A5B4FC',
400: '#818CF8',
500: '#6366F1',
600: '#4F46E5',
700: '#4338CA',
800: '#3730A3',
900: '#312E81',
},
success: '#10B981',
warning: '#F59E0B',
error: '#EF4444',
},
// Custom font families
fontFamily: {
sans: ['Inter', 'system-ui', 'sans-serif'],
mono: ['JetBrains Mono', 'monospace'],
},
// Custom spacing
spacing: {
'18': '4.5rem',
'88': '22rem',
},
// Custom border radius
borderRadius: {
'4xl': '2rem',
},
// Custom shadows
boxShadow: {
'soft': '0 2px 15px -3px rgba(0, 0, 0, 0.07), 0 10px 20px -2px rgba(0, 0, 0, 0.04)',
'card': '0 0 0 1px rgba(0, 0, 0, 0.05), 0 2px 4px rgba(0, 0, 0, 0.1)',
},
// Custom animations
animation: {
'fade-in': 'fadeIn 0.3s ease-out',
'slide-up': 'slideUp 0.3s ease-out',
'bounce-soft': 'bounceSoft 0.5s ease-in-out',
},
keyframes: {
fadeIn: {
'0%': { opacity: '0' },
'100%': { opacity: '1' },
},
slideUp: {
'0%': { opacity: '0', transform: 'translateY(10px)' },
'100%': { opacity: '1', transform: 'translateY(0)' },
},
bounceSoft: {
'0%, 100%': { transform: 'scale(1)' },
'50%': { transform: 'scale(1.05)' },
},
},
},
},
plugins: [],
}
module.exports = function (api) {
api.cache(true)
return {
presets: [
['babel-preset-expo', { jsxImportSource: 'nativewind' }],
'nativewind/babel',
],
plugins: [
// If using Reanimated
'react-native-reanimated/plugin',
],
}
}
const { getDefaultConfig } = require('expo/metro-config')
const { withNativeWind } = require('nativewind/metro')
const config = getDefaultConfig(__dirname)
module.exports = withNativeWind(config, { input: './global.css' })
@tailwind base;
@tailwind components;
@tailwind utilities;
/* Custom base styles */
@layer base {
/* Default body styles */
* {
@apply border-border;
}
}
/* Custom component classes */
@layer components {
.btn-primary {
@apply bg-primary-500 text-white px-4 py-2 rounded-lg font-medium active:bg-primary-600;
}
.card {
@apply bg-white dark:bg-gray-800 rounded-xl p-4 shadow-card;
}
.input {
@apply bg-gray-50 dark:bg-gray-900 border border-gray-200 dark:border-gray-700 rounded-lg px-4 py-3 text-base;
}
}
/* Custom utilities */
@layer utilities {
.text-balance {
text-wrap: balance;
}
}
// src/app/_layout.tsx
import '../global.css'
import { Stack } from 'expo-router'
import { useColorScheme } from 'nativewind'
export default function RootLayout() {
const { colorScheme, setColorScheme } = useColorScheme()
return (
<Stack
screenOptions={{
headerShown: false,
contentStyle: {
backgroundColor: colorScheme === 'dark' ? '#0F172A' : '#FFFFFF',
},
}}
/>
)
}
import { View, Text, Pressable } from 'react-native'
export function Button({ title, onPress, variant = 'primary' }) {
const variants = {
primary: 'bg-primary-500 active:bg-primary-600',
secondary: 'bg-gray-100 dark:bg-gray-800 active:bg-gray-200',
outline: 'border-2 border-primary-500 bg-transparent',
}
const textVariants = {
primary: 'text-white',
secondary: 'text-gray-900 dark:text-white',
outline: 'text-primary-500',
}
return (
<Pressable
className={`px-6 py-3 rounded-xl items-center justify-center ${variants[variant]}`}
onPress={onPress}
>
<Text className={`font-semibold text-base ${textVariants[variant]}`}>
{title}
</Text>
</Pressable>
)
}
import { View, Text, Image, Pressable } from 'react-native'
import { Ionicons } from '@expo/vector-icons'
interface LessonCardProps {
title: string
duration: string
completed: boolean
onPress: () => void
}
export function LessonCard({ title, duration, completed, onPress }: LessonCardProps) {
return (
<Pressable
className="flex-row items-center p-4 bg-white dark:bg-gray-800 rounded-2xl shadow-soft active:scale-[0.98] transition-transform"
onPress={onPress}
>
<View className="w-14 h-14 rounded-xl bg-primary-100 dark:bg-primary-900 items-center justify-center">
<Ionicons
name="book"
size={24}
className="text-primary-500"
/>
</View>
<View className="flex-1 ml-4">
<Text className="text-base font-semibold text-gray-900 dark:text-white">
{title}
</Text>
<Text className="text-sm text-gray-500 dark:text-gray-400 mt-1">
{duration}
</Text>
</View>
{completed && (
<View className="w-8 h-8 rounded-full bg-success items-center justify-center">
<Ionicons name="checkmark" size={18} color="white" />
</View>
)}
</Pressable>
)
}
import { Pressable } from 'react-native'
import { useColorScheme } from 'nativewind'
import { Ionicons } from '@expo/vector-icons'
export function ThemeToggle() {
const { colorScheme, toggleColorScheme } = useColorScheme()
return (
<Pressable
className="w-10 h-10 rounded-full bg-gray-100 dark:bg-gray-800 items-center justify-center"
onPress={toggleColorScheme}
>
<Ionicons
name={colorScheme === 'dark' ? 'sunny' : 'moon'}
size={20}
className="text-gray-600 dark:text-gray-300"
/>
</Pressable>
)
}
import { View, Text } from 'react-native'
export function ResponsiveGrid({ items }) {
return (
<View className="flex-row flex-wrap -mx-2">
{items.map((item) => (
<View
key={item.id}
// Responsive width: full on mobile, half on tablet, third on desktop
className="w-full sm:w-1/2 lg:w-1/3 px-2 mb-4"
>
<View className="bg-white dark:bg-gray-800 rounded-xl p-4">
<Text className="font-semibold">{item.title}</Text>
</View>
</View>
))}
</View>
)
}
npm install clsx
import { View, Text, Pressable } from 'react-native'
import clsx from 'clsx'
interface ChipProps {
label: string
selected: boolean
onPress: () => void
}
export function Chip({ label, selected, onPress }: ChipProps) {
return (
<Pressable
className={clsx(
'px-4 py-2 rounded-full border-2 transition-colors',
selected
? 'bg-primary-500 border-primary-500'
: 'bg-transparent border-gray-300 dark:border-gray-600'
)}
onPress={onPress}
>
<Text
className={clsx(
'font-medium',
selected ? 'text-white' : 'text-gray-700 dark:text-gray-300'
)}
>
{label}
</Text>
</Pressable>
)
}
npm install class-variance-authority
import { cva, type VariantProps } from 'class-variance-authority'
import { Pressable, Text } from 'react-native'
const buttonVariants = cva(
'flex-row items-center justify-center rounded-xl font-semibold',
{
variants: {
variant: {
primary: 'bg-primary-500 active:bg-primary-600',
secondary: 'bg-gray-100 dark:bg-gray-800',
outline: 'border-2 border-primary-500 bg-transparent',
ghost: 'bg-transparent',
},
size: {
sm: 'px-3 py-2 text-sm',
md: 'px-4 py-3 text-base',
lg: 'px-6 py-4 text-lg',
},
},
defaultVariants: {
variant: 'primary',
size: 'md',
},
}
)
interface ButtonProps extends VariantProps<typeof buttonVariants> {
title: string
onPress: () => void
}
export function Button({ title, variant, size, onPress }: ButtonProps) {
return (
<Pressable className={buttonVariants({ variant, size })} onPress={onPress}>
<Text
className={clsx(
'font-semibold',
variant === 'primary' ? 'text-white' : 'text-gray-900 dark:text-white'
)}
>
{title}
</Text>
</Pressable>
)
}
/// <reference types="nativewind/types" />
declare module 'nativewind' {
interface NativeWindStyleSheet {
// Add custom utilities if needed
}
}
{
"tailwindCSS.classAttributes": ["className", "class", "tw"],
"tailwindCSS.experimental.classRegex": [
["cva\\(([^)]*)\\)", "[\"'`]([^\"'`]*).*?[\"'`]"],
["cn\\(([^)]*)\\)", "[\"'`]([^\"'`]*).*?[\"'`]"]
],
"editor.quickSuggestions": {
"strings": true
}
}
For: $ARGUMENTS
Provide: