Back to Guide
React Native and Native Wind Setup
1. Create a new expo project using pnpm
# Create a new Expo project with TypeScript template
pnpm create expo-app my-grocery-app
cd my-grocery-app
# Start the development server
pnpm start
2. Install NativeWind via their docs
Step 2.1: Install NativeWind and dependencies
# Install NativeWind and required dependencies
pnpm install nativewind react-native-reanimated@~3.17.4 react-native-safe-area-context@5.4.0
pnpm install -D tailwindcss@^3.4.17 prettier-plugin-tailwindcss@^0.5.11
Step 2.2: Initialize Tailwind CSS
# Initialize Tailwind config
npx tailwindcss init
Step 2.3: Configure tailwind.config.js
/** @type {import('tailwindcss').Config} */
module.exports = {
content: [
"./App.{js,jsx,ts,tsx}",
"./app/**/*.{js,jsx,ts,tsx}",
"./components/**/*.{js,jsx,ts,tsx}",
],
presets: [require("nativewind/preset")],
theme: {
extend: {},
},
plugins: [],
};
Step 2.4: Add Babel preset (babel.config.js)
module.exports = function (api) {
api.cache(true);
return {
presets: [
["babel-preset-expo", { jsxImportSource: "nativewind" }],
"nativewind/babel",
],
};
};
Step 2.5: Configure Metro (metro.config.js)
Create a metro.config.js
file in the root of your project if you don't already have one, then add the following configuration:
const { getDefaultConfig } = require("expo/metro-config");
const { withNativeWind } = require("nativewind/metro");
const config = getDefaultConfig(__dirname);
module.exports = withNativeWind(config, { input: "./global.css" });
Step 2.6: Create global.css
@tailwind base;
@tailwind components;
@tailwind utilities;
Step 2.6: Update app/_layout.tsx
import "../global.css";
import { useFonts } from "expo-font";
import { Stack } from "expo-router";
import * as SplashScreen from "expo-splash-screen";
import { useEffect } from "react";
// Prevent the splash screen from auto-hiding
SplashScreen.preventAutoHideAsync();
export default function RootLayout() {
const [loaded] = useFonts({
SpaceMono: require("../assets/fonts/SpaceMono-Regular.ttf"),
});
useEffect(() => {
if (loaded) {
SplashScreen.hideAsync();
}
}, [loaded]);
if (!loaded) {
return null;
}
return (
<Stack>
<Stack.Screen name="(tabs)" options={{ headerShown: false }} />
</Stack>
);
}
TypeScript setup (optional)
If you're using TypeScript in your project, you'll need to set up the type definitions. Nativewind extends the React Native types via declaration merging. The simplest method to include the types is to create a new nativewind-env.d.ts file and add a triple-slash directive referencing the types.
/// <reference types="nativewind/types" />
3. Create the pages
Step 3.1: Create page structure in app/(tabs)/
Create these files in your app/(tabs)/
directory:
app/(tabs)/
├── index.tsx (Home)
├── browse.tsx (Browse)
├── deals.tsx (Deals)
├── cart.tsx (Cart)
├── account.tsx (Account)
└── explore.tsx (Explore - if needed)
Step 3.2: Home page (app/(tabs)/index.tsx)
import React from "react";
import { View, Text, ScrollView, SafeAreaView } from "react-native";
export default function HomeScreen() {
return (
<SafeAreaView className="flex-1 bg-white">
<ScrollView className="flex-1 px-4">
<View className="mt-5 mb-8">
<Text className="text-2xl font-bold text-gray-900 mb-2">
Welcome back! 👋
</Text>
<Text className="text-base text-gray-600">
Discover fresh arrivals and great deals
</Text>
</View>
{/* Add your home content here */}
<View className="bg-gray-50 rounded-2xl p-4 mb-4">
<Text className="text-lg font-semibold text-gray-900">
New Arrivals
</Text>
</View>
</ScrollView>
</SafeAreaView>
);
}
Step 3.3: Browse page (app/(tabs)/browse.tsx)
import React from "react";
import { View, Text, SafeAreaView } from "react-native";
export default function BrowseScreen() {
return (
<SafeAreaView className="flex-1 bg-white">
<View className="flex-1 px-4 pt-5">
<Text className="text-2xl font-bold text-gray-900 mb-4">
Browse Categories
</Text>
{/* Add your browse content here */}
<View className="bg-gray-50 rounded-2xl p-4">
<Text className="text-lg font-semibold text-gray-900">
Search and browse products
</Text>
</View>
</View>
</SafeAreaView>
);
}
Step 3.4: Deals page (app/(tabs)/deals.tsx)
import React from "react";
import { View, Text, SafeAreaView } from "react-native";
export default function DealsScreen() {
return (
<SafeAreaView className="flex-1 bg-white">
<View className="flex-1 px-4 pt-5">
<Text className="text-2xl font-bold text-gray-900 mb-4">
Special Deals 🏷️
</Text>
{/* Add your deals content here */}
<View className="bg-green-50 rounded-2xl p-4">
<Text className="text-lg font-semibold text-green-800">
Today's best offers
</Text>
</View>
</View>
</SafeAreaView>
);
}
Step 3.5: Cart page (app/(tabs)/cart.tsx)
import React from "react";
import { View, Text, SafeAreaView } from "react-native";
export default function CartScreen() {
return (
<SafeAreaView className="flex-1 bg-white">
<View className="flex-1 px-4 pt-5">
<Text className="text-2xl font-bold text-gray-900 mb-4">
Shopping Cart 🛒
</Text>
{/* Add your cart content here */}
<View className="bg-gray-50 rounded-2xl p-4">
<Text className="text-lg font-semibold text-gray-900">
Your cart is empty
</Text>
</View>
</View>
</SafeAreaView>
);
}
Step 3.6: Account page (app/(tabs)/account.tsx)
import React from "react";
import { View, Text, SafeAreaView } from "react-native";
export default function AccountScreen() {
return (
<SafeAreaView className="flex-1 bg-white">
<View className="flex-1 px-4 pt-5">
<Text className="text-2xl font-bold text-gray-900 mb-4">
My Account 👤
</Text>
{/* Add your account content here */}
<View className="bg-gray-50 rounded-2xl p-4">
<Text className="text-lg font-semibold text-gray-900">
Profile settings and preferences
</Text>
</View>
</View>
</SafeAreaView>
);
}
4. Create Colors object and Update with your colors
Step 4.1: Create constants/Colors.ts
/**
* Below are the colors that are used in the app. The colors are defined in the light and dark mode.
*/
const tintColorLight = "#067661";
const tintColorDark = "#00B050";
export const Colors = {
light: {
text: "#11181C",
background: "#FFFFFF",
tint: tintColorLight,
icon: "#687076",
tabIconDefault: "#9CA3AF",
tabIconSelected: "#165e51",
tabBarBackground: "#FFFFFF",
tabBarBorder: "#F3F4F6",
// Additional grocery app colors
primary: "#067661",
primaryDark: "#165e51",
secondary: "#00B050",
inactive: "#9CA3AF",
success: "#10B981",
warning: "#F59E0B",
error: "#EF4444",
cardBackground: "#F9FAFB",
border: "#E5E7EB",
},
dark: {
text: "#ECEDEE",
background: "#151718",
tint: tintColorDark,
icon: "#9BA1A6",
tabIconDefault: "#6B7280",
tabIconSelected: "#00B050",
tabBarBackground: "#1F2937",
tabBarBorder: "#374151",
// Additional grocery app colors
primary: "#00B050",
primaryDark: "#059669",
secondary: "#067661",
inactive: "#6B7280",
success: "#10B981",
warning: "#F59E0B",
error: "#EF4444",
cardBackground: "#374151",
border: "#4B5563",
},
};
Step 4.2: Update tailwind.config.js with custom colors
/** @type {import('tailwindcss').Config} */
module.exports = {
content: [
"./App.{js,jsx,ts,tsx}",
"./app/**/*.{js,jsx,ts,tsx}",
"./components/**/*.{js,jsx,ts,tsx}",
],
presets: [require("nativewind/preset")],
theme: {
extend: {
colors: {
primary: {
DEFAULT: "#067661",
dark: "#165e51",
},
secondary: "#00B050",
grocery: {
green: "#067661",
lightGreen: "#00B050",
gray: "#9CA3AF",
lightGray: "#F9FAFB",
},
},
},
},
plugins: [],
};
5. Create the Navigation tabs
Step 5.1: Update IconSymbol component (components/ui/IconSymbol.tsx)
// IconSymbol component using Material Icons from Expo Vector Icons
import MaterialIcons from "@expo/vector-icons/MaterialIcons";
import { SymbolWeight } from "expo-symbols";
import { ComponentProps } from "react";
import { OpaqueColorValue, type StyleProp, type TextStyle } from "react-native";
type IconMapping = Record<string, ComponentProps<typeof MaterialIcons>["name"]>;
type IconSymbolName = keyof typeof MAPPING;
/**
* Add your SF Symbols to Material Icons mappings here.
* - see Material Icons in the [Icons Directory](https://icons.expo.fyi).
* - see SF Symbols in the [SF Symbols](https://developer.apple.com/sf-symbols/) app.
*/
const MAPPING = {
// Navigation icons
"house.fill": "home",
"paperplane.fill": "send",
magnifyingglass: "search",
"tag.fill": "local-offer",
"cart.fill": "shopping-cart",
"person.fill": "person",
"explore.fill": "explore",
// UI and utility icons
"square.grid.2x2": "grid-view",
"heart.fill": "favorite",
heart: "favorite-border",
plus: "add",
minus: "remove",
"star.fill": "star",
star: "star-border",
"chevron.right": "chevron-right",
"chevron.left": "chevron-left",
"chevron.left.forwardslash.chevron.right": "code",
// Common app icons
"location.fill": "location-on",
location: "location-on",
"clock.fill": "access-time",
"bell.fill": "notifications",
bell: "notifications-none",
"gearshape.fill": "settings",
"line.horizontal.3": "menu",
xmark: "close",
"arrow.left": "arrow-back",
"arrow.right": "arrow-forward",
"arrow.up": "arrow-upward",
"arrow.down": "arrow-downward",
// Action icons
"eye.fill": "visibility",
"eye.slash.fill": "visibility-off",
checkmark: "check",
"exclamationmark.circle.fill": "error",
"info.circle.fill": "info",
"trash.fill": "delete",
pencil: "edit",
"camera.fill": "camera-alt",
"photo.fill": "photo",
// Filter and sort
"line.horizontal.3.decrease.circle": "filter-list",
"arrow.up.arrow.down": "sort",
// Transfer and sharing
"square.and.arrow.up": "share",
"bookmark.fill": "bookmark",
bookmark: "bookmark-border",
"arrow.clockwise": "refresh",
// Communication icons
"phone.fill": "phone",
"envelope.fill": "email",
"message.circle.fill": "chat",
// Shopping specific
"bag.fill": "shopping-bag",
"creditcard.fill": "credit-card",
percent: "percent",
"dollarsign.circle.fill": "attach-money",
// Categories
"leaf.fill": "eco",
"flame.fill": "whatshot",
snowflake: "ac-unit",
"drop.fill": "water-drop",
// Additional utility
"questionmark.circle.fill": "help",
"plus.circle.fill": "add-circle",
"minus.circle.fill": "remove-circle",
"play.fill": "play-arrow",
"pause.fill": "pause",
"stop.fill": "stop",
} as IconMapping;
/**
* An icon component that uses Material Icons from Expo Vector Icons.
* This ensures a consistent look across platforms with great icon coverage.
* Icon `name`s are based on SF Symbols and mapped to Material Icons.
*/
export function IconSymbol({
name,
size = 24,
color,
style,
weight,
}: {
name: IconSymbolName;
size?: number;
color: string | OpaqueColorValue;
style?: StyleProp<TextStyle>;
weight?: SymbolWeight;
}) {
const materialIconName = MAPPING[name];
if (!materialIconName) {
console.warn(`IconSymbol: Icon "${name}" not found in mapping`);
// Return a fallback icon
return (
<MaterialIcons
color={color}
size={size}
name="help-outline"
style={style}
/>
);
}
return (
<MaterialIcons
color={color}
size={size}
name={materialIconName}
style={style}
/>
);
}
// Export the available icon names for TypeScript support
export type IconName = IconSymbolName;
Step 5.2: Update Tab Layout (app/(tabs)/_layout.tsx)
import { Tabs } from "expo-router";
import React from "react";
import { Platform } from "react-native";
import { HapticTab } from "@/components/HapticTab";
import { IconSymbol } from "@/components/ui/IconSymbol";
import TabBarBackground from "@/components/ui/TabBarBackground";
import { Colors } from "@/constants/Colors";
import { useColorScheme } from "@/hooks/useColorScheme";
export default function TabLayout() {
const colorScheme = useColorScheme();
return (
<Tabs
screenOptions={{
tabBarActiveTintColor: Colors[colorScheme ?? "light"].secondary,
tabBarInactiveTintColor: Colors[colorScheme ?? "light"].tabIconDefault,
headerShown: false,
tabBarButton: HapticTab,
tabBarBackground: TabBarBackground,
tabBarStyle: {
borderTopWidth: 0.5,
borderTopColor: Colors[colorScheme ?? "light"].tabBarBorder,
height: Platform.OS === "ios" ? 88 : 120,
paddingBottom: Platform.OS === "ios" ? 25 : 12,
paddingTop: 8,
paddingHorizontal: 16,
shadowColor: "#000",
shadowOffset: {
width: 0,
height: -2,
},
shadowOpacity: 0.05,
shadowRadius: 8,
elevation: 8,
...Platform.select({
ios: {
position: "absolute",
backgroundColor: `${
Colors[colorScheme ?? "light"].tabBarBackground
}F8`, // 97% opacity
backdropFilter: "blur(20px)",
},
default: {
backgroundColor: Colors[colorScheme ?? "light"].tabBarBackground,
},
}),
},
tabBarLabelStyle: {
fontSize: 11,
fontWeight: "500",
marginTop: 4,
fontFamily: Platform.OS === "ios" ? "System" : "Roboto",
},
tabBarIconStyle: {
marginTop: 2,
},
}}
>
<Tabs.Screen
name="index"
options={{
title: "Home",
tabBarIcon: ({ color, focused }) => (
<IconSymbol
size={22}
name="house.fill"
color={
focused
? Colors[colorScheme ?? "light"].tabIconSelected
: Colors[colorScheme ?? "light"].tabIconDefault
}
/>
),
}}
/>
<Tabs.Screen
name="browse"
options={{
title: "Browse",
tabBarIcon: ({ color, focused }) => (
<IconSymbol
size={22}
name="magnifyingglass"
color={
focused
? Colors[colorScheme ?? "light"].tabIconSelected
: Colors[colorScheme ?? "light"].tabIconDefault
}
/>
),
}}
/>
<Tabs.Screen
name="deals"
options={{
title: "Deal",
tabBarIcon: ({ color, focused }) => (
<IconSymbol
size={22}
name="tag.fill"
color={
focused
? Colors[colorScheme ?? "light"].tabIconSelected
: Colors[colorScheme ?? "light"].tabIconDefault
}
/>
),
}}
/>
<Tabs.Screen
name="cart"
options={{
title: "Cart",
tabBarIcon: ({ color, focused }) => (
<IconSymbol
size={22}
name="cart.fill"
color={
focused
? Colors[colorScheme ?? "light"].tabIconSelected
: Colors[colorScheme ?? "light"].tabIconDefault
}
/>
),
}}
/>
<Tabs.Screen
name="account"
options={{
title: "Account",
tabBarIcon: ({ color, focused }) => (
<IconSymbol
size={22}
name="person.fill"
color={
focused
? Colors[colorScheme ?? "light"].tabIconSelected
: Colors[colorScheme ?? "light"].tabIconDefault
}
/>
),
}}
/>
<Tabs.Screen
name="explore"
options={{
title: "Explore",
// href: null,
tabBarIcon: ({ color, focused }) => (
<IconSymbol
size={22}
name="explore.fill"
color={
focused
? Colors[colorScheme ?? "light"].tabIconSelected
: Colors[colorScheme ?? "light"].tabIconDefault
}
/>
),
}}
/>
</Tabs>
);
}
6. Additional Setup Steps
Step 6.1: Configure TypeScript for NativeWind
Create or update nativewind-env.d.ts
:
/// <reference types="nativewind/types" />
Step 6.2: Update app.json
- Switch to metro
{
"expo": {
"web": {
"bundler": "metro"
}
}
}
Step 6.3: Test your setup
# Clear cache and restart
pnpm start --clear
# Or for specific platforms
pnpm run ios
pnpm run android
pnpm run web
7. Common Issues and Solutions
Issue 1: NativeWind styles not applying
- Make sure
global.css
is imported in_layout.tsx
- Verify Metro config includes NativeWind
- Clear Metro cache:
pnpm start --clear
Issue 2: TypeScript errors
- Ensure
nativewind-env.d.ts
is created - Update tsconfig.json includes
Issue 3: Navigation not working
- Check that all page files exist in
app/(tabs)/
- Verify icon mappings in IconSymbol component
8. Next Steps
- Add components: Create reusable UI components
- State management: Add Zustand or Redux for global state
- API integration: Connect to backend services
- Testing: Add Jest and React Native Testing Library
- Build optimization: Configure for production builds
Useful Commands
# Development
pnpm start
pnpm run ios
pnpm run android
pnpm run web
# Building
pnpm run build
expo build
# Useful tools
npx expo install --fix
npx expo doctor
Subscribe to My Latest Guides
All the latest Guides and tutorials, straight from the team.