Mastering Navigation in React Native: Stack, Tabs, and More

by Didin J. on May 22, 2025 Mastering Navigation in React Native: Stack, Tabs, and More

Master React Native Navigation with custom animations, stack transitions, tabs, and drawers using @react-navigation/native-stack

React Native makes it easy to build native mobile apps using JavaScript and React. But as your app grows, so does the need for structured navigation. Whether you’re switching screens, managing nested routes, or implementing a tab layout, navigation plays a key role in building intuitive mobile experiences.

In this tutorial, you'll learn how to set up navigation in a React Native app using the popular React Navigation library, with examples of stack navigation, tab navigation, and drawer navigation.


Prerequisites

Before you begin, make sure you have the following installed:

  • Node.js and npm
  • React Native CLI or Expo CLI
  • A physical device or emulator (Android or iOS)

Create a New React Native App

For this tutorial, we’ll use React Native CLI. Run:

npx @react-native-community/cli init RNNavigationExample
cd RNNavigationExample

Alternatively, if you prefer Expo, run:

npx create-expo-app RNNavigationExample
cd RNNavigationExample


Installing React Navigation

Install the core libraries:

npm install @react-navigation/native

Then install the required dependencies:

npm install react-native-screens react-native-safe-area-context react-native-gesture-handler react-native-reanimated

For stack, tab, and drawer navigators:

npm install @react-navigation/native-stack
npm install @react-navigation/bottom-tabs
npm install @react-navigation/drawer

To complete setup of react-native-reanimated, in babel.config.js add: 

module.exports = {
  presets: ['module:@react-native/babel-preset'],
  plugins: ['react-native-reanimated/plugin'], 
};

Don’t forget to wrap your app in the navigation provider:

import * as React from 'react';
import { NavigationContainer } from '@react-navigation/native';

export default function App() {
  return (
    <NavigationContainer>
      {/* Navigators will go here */}
    </NavigationContainer>
  );
}

To keep things organized, you can use this folder layout:

/RNNavigationExample
├── /screens
│   ├── HomeScreen.js
│   └── DetailsScreen.js
├── /navigators
│   ├── StackNavigator.js
│   ├── TabNavigator.js
│   └── DrawerNavigator.js
└── App.tsx


Stack Navigation Example

Stack navigation lets you push and pop screens like a stack of cards. Create a new navigators folder and StackNavigator.js file, then add:

import { createNativeStackNavigator } from '@react-navigation/native-stack';
import HomeScreen from './screens/HomeScreen';
import DetailsScreen from './screens/DetailsScreen';

const Stack = createNativeStackNavigator();

export default function StackNavigator() {
  return (
    <Stack.Navigator initialRouteName="Home">
      <Stack.Screen name="Home" component={HomeScreen} />
      <Stack.Screen name="Details" component={DetailsScreen} />
    </Stack.Navigator>
  );
}

Create a new folder, screens, and HomeScreen.js, then add:

import React from 'react';
import { View, Text, Button } from 'react-native';
import PropTypes from 'prop-types';

const HomeScreen = ({ navigation }) => {
  return (
    <View style={{ flex: 1, justifyContent: 'center', alignItems: 'center' }}>
      <Text>Welcome to the Home Screen</Text>
      <Button
        title="Go to Details"
        onPress={() => navigation.navigate('Details', { id: 42 })}
      />
    </View>
  );
};

HomeScreen.propTypes = {
  navigation: PropTypes.object.isRequired,
};

export default HomeScreen;

Create a new DetailsScreen.js, then add:

import React from 'react';
import { View, Text } from 'react-native';
import PropTypes from 'prop-types';

const DetailsScreen = ({ route }) => {
  const { id } = route.params || {};

  return (
    <View style={{ flex: 1, justifyContent: 'center', alignItems: 'center' }}>
      <Text>Details Screen</Text>
      <Text>ID: {id}</Text>
    </View>
  );
};

DetailsScreen.propTypes = {
  route: PropTypes.shape({
    params: PropTypes.shape({
      id: PropTypes.number,
    }),
  }).isRequired,
};

export default DetailsScreen;

Update the App.tsx.

import * as React from 'react';
import { NavigationContainer } from '@react-navigation/native';
import StackNavigator from './StackNavigator';

export default function App() {
  return (
    <NavigationContainer>
      <StackNavigator />
    </NavigationContainer>
  );
}

Run the application on the device.

npx react-native run-android

Mastering Navigation in React Native: Stack, Tabs, and More - stack


Tab Navigation Example

Tab navigation allows users to switch between different sections of the app. Create a new navigators/TabNavigator.js file, then add:

import { createBottomTabNavigator } from '@react-navigation/bottom-tabs';
import HomeScreen from './screens/HomeScreen';
import SettingsScreen from './screens/SettingsScreen';

const Tab = createBottomTabNavigator();

export default function TabNavigator() {
  return (
    <Tab.Navigator>
      <Tab.Screen name="Home" component={HomeScreen} />
      <Tab.Screen name="Settings" component={SettingsScreen} />
    </Tab.Navigator>
  );
}

Create a new screens/SettingsScreen.js, then add:

import React from 'react';
import { View, Text } from 'react-native';

const SettingsScreen = () => {
  return (
    <View style={{ flex: 1, justifyContent: 'center', alignItems: 'center' }}>
      <Text>Settings Screen</Text>
    </View>
  );
};

export default SettingsScreen;

Update the App.tsx.

import * as React from 'react';
import { NavigationContainer } from '@react-navigation/native';
import TabNavigator from './navigators/TabNavigator';

export default function App() {
  return (
    <NavigationContainer>
      <TabNavigator />
    </NavigationContainer>
  );
}

Mastering Navigation in React Native: Stack, Tabs, and More - tabs


Drawer Navigation Example

Drawer navigation enables a sliding menu from the side, which is commonly used for navigation drawers. Create a new navigators/DrawerNavigator.js, then add:

import { createDrawerNavigator } from '@react-navigation/drawer';
import HomeScreen from './screens/HomeScreen';
import ProfileScreen from './screens/ProfileScreen';

const Drawer = createDrawerNavigator();

export default function DrawerNavigator() {
  return (
    <Drawer.Navigator>
      <Drawer.Screen name="Home" component={HomeScreen} />
      <Drawer.Screen name="Profile" component={ProfileScreen} />
    </Drawer.Navigator>
  );
}

Create a new screens/ProfileScreen.js, then add:

import React from 'react';
import { View, Text, StyleSheet } from 'react-native';

const ProfileScreen = () => {
  return (
    <View style={styles.container}>
      <Text>Profile Screen</Text>
    </View>
  );
};

const styles = StyleSheet.create({
  container: {
    flex: 1,
    justifyContent: 'center',
    alignItems: 'center',
  },
});

export default ProfileScreen;

Update the App.tsx.

import * as React from 'react';
import { NavigationContainer } from '@react-navigation/native';
import DrawerNavigator from './navigators/DrawerNavigator';

export default function App() {
  return (
    <NavigationContainer>
      <DrawerNavigator />
    </NavigationContainer>
  );
}

Mastering Navigation in React Native: Stack, Tabs, and More - drawer


Combining Navigators (Advanced)

You can also nest navigators to create complex navigation flows, such as a tab bar with stack screens inside each tab. Update navigators/TabNavigator.js:

import { createBottomTabNavigator } from '@react-navigation/bottom-tabs';
import StackNavigator from './StackNavigator';
import DrawerNavigator from './DrawerNavigator';

const Tab = createBottomTabNavigator();

export default function TabNavigator() {
  return (
    <Tab.Navigator>
      <Tab.Screen name="Home" component={StackNavigator} />
      <Tab.Screen name="Settings" component={DrawerNavigator} />
    </Tab.Navigator>
  );
}

Update the App.tsx.

import * as React from 'react';
import { NavigationContainer } from '@react-navigation/native';
import TabNavigator from './navigators/TabNavigator';

export default function App() {
  return (
    <NavigationContainer>
      <TabNavigator />
    </NavigationContainer>
  );
}


Adding Deep Linking to React Native Navigation

Deep linking allows your app to respond to URLs — for example, opening myapp://details/42 can take users directly to the Details screen with an ID of 42.

  • This is especially useful for:
  • Push notifications
  • Opening screens from email or external links
  • Sharing specific content in your app

Step 1: Configure URL Scheme

For deep linking to work, you must define a custom URL scheme in your native code.

On Android

Edit android/app/src/main/AndroidManifest.xml:

<intent-filter>
    <action android:name="android.intent.action.VIEW" />
    <category android:name="android.intent.category.DEFAULT" />
    <category android:name="android.intent.category.BROWSABLE" />

    <data android:scheme="myapp" android:host="app" />
</intent-filter>

On iOS

In ios/[yourProject]/Info.plist, add:

<key>CFBundleURLTypes</key>
<array>
  <dict>
    <key>CFBundleURLSchemes</key>
    <array>
      <string>myapp</string>
    </array>
  </dict>
</array>

Step 2: Set Up Linking Configuration in React Navigation

Create a linking configuration in your App.tsx:

import * as React from 'react';
import { NavigationContainer } from '@react-navigation/native';
import StackNavigator from './navigators/StackNavigator';

export default function App() {
  const linking = {
    prefixes: ['myapp://'],
    config: {
      screens: {
        Home: 'home',
        Details: 'details/:id',
        Profile: 'user/:username',
      },
    },
  };

  return (
    <NavigationContainer linking={linking}>
      <StackNavigator />
    </NavigationContainer>
  );
}

Step 3: Test Deep Links

You can simulate deep links in development:

Android

adb shell am start -W -a android.intent.action.VIEW -d "myapp://details/42" com.rnnavigationexample

iOS (Simulator)

xcrun simctl openurl booted "myapp://details/42"

If your DetailsScreen uses route params:

function DetailsScreen({ route }) {
  const { id } = route.params;
  return <Text>Detail ID: {id}</Text>;
}

Result

Now, when you open myapp://details/42, your app will launch (if installed) and navigate directly to the Details screen with id = 42.

Prerequisites

Ensure you’re using:

@react-navigation/native-stack (which uses react-native-screens)

Or

react-navigation/stack with gesture and animation support

Since you're using @react-navigation/native-stack, we’ll use that for smooth and performant native-style animations.

Step 1: Enable Native Stack Navigation Transitions

If you're not already doing this, make sure your stack navigator is using createNativeStackNavigator:

import { createNativeStackNavigator } from '@react-navigation/native-stack';

const Stack = createNativeStackNavigator();

Step 2: Apply Custom Animations per Screen

You can customize animations per screen using the animation, animationTypeForReplace, and gestureDirection options.

Example: Slide from the bottom

    <Stack.Navigator initialRouteName="Home">
      <Stack.Screen name="Home" component={HomeScreen} />
      <Stack.Screen
        name="Details"
        component={DetailsScreen}
        options={{
          animation: 'slide_from_bottom',
        }}
      />
    </Stack.Navigator>

Other animation values:

  • 'default'
  • 'fade'
  • 'flip'
  • 'none'
  • 'slide_from_right'
  • 'slide_from_left'
  • 'slide_from_bottom'
  • 'fade_from_bottom'

Step 3: Set Global Stack Animations

To use a consistent animation throughout your stack:

<Stack.Navigator
  screenOptions={{
    animation: 'slide_from_right',
    gestureEnabled: true,
  }}
>
  <Stack.Screen name="Home" component={HomeScreen} />
  <Stack.Screen name="Details" component={DetailsScreen} />
</Stack.Navigator>

Bonus: Conditional Animation Example

    <Stack.Screen
      name="Profile"
      component={ProfileScreen}
      options={({ route }) => ({
        animation: route.params?.fromSettings ? 'slide_from_bottom' : 'fade',
      })}
    />

For Tabs and Drawers

Tabs and drawers come with built-in animations, but you can adjust some transition settings like this:

<Tab.Navigator
  screenOptions={{
    tabBarHideOnKeyboard: true,
    tabBarStyle: {
      transitionDuration: '0.3s',
    },
  }}
>

For drawers, animation is built-in, but you can use:

<Drawer.Navigator
  screenOptions={{
    drawerType: 'slide', // or 'front', 'back', 'permanent'
  }}
>


Styling Navigation Components in React Native

In this section, we’ll style the header, tab bar, and drawer using options provided by React Navigation.

Stack Navigator Styling

Customize the stack headers using screenOptions.

<Stack.Navigator
  screenOptions={{
    headerStyle: {
      backgroundColor: '#6200ee',
    },
    headerTintColor: '#fff',
    headerTitleStyle: {
      fontWeight: 'bold',
    },
    contentStyle: {
      backgroundColor: '#f5f5f5',
    },
  }}
>
  <Stack.Screen name="Home" component={HomeScreen} />
  <Stack.Screen name="Details" component={DetailsScreen} />
</Stack.Navigator>

Tab Navigator Styling

You can style the tab bar and active/inactive icons like this:

     <Tab.Navigator
        screenOptions={{
          tabBarHideOnKeyboard: true,
          tabBarStyle: {
            transitionDuration: '0.3s',
            backgroundColor: '#fff',
            borderTopWidth: 0,
            elevation: 10,
          },
          tabBarActiveTintColor: '#6200ee',
          tabBarInactiveTintColor: '#888',
          tabBarLabelStyle: {
            fontSize: 12,
          },
          headerShown: false,
        }}
      >
      <Tab.Screen name="Home" component={StackNavigator} />
      <Tab.Screen name="Settings" component={DrawerNavigator} />
    </Tab.Navigator>

Drawer Navigator Styling

     <Drawer.Navigator
        screenOptions={{
            drawerType: 'slide',
            drawerStyle: {
                backgroundColor: '#f0f0f0',
                width: 240,
            },
            drawerActiveBackgroundColor: '#6200ee',
            drawerActiveTintColor: '#fff',
            drawerInactiveTintColor: '#333',
            headerStyle: {
                backgroundColor: '#6200ee',
            },
            headerTintColor: '#fff',
        }}
        >
      <Drawer.Screen name="Home" component={HomeScreen} />
      <Drawer.Screen name="Profile" component={ProfileScreen} />
    </Drawer.Navigator>

Tips for Clean UI

  • Stick to your design system or use a color theme consistently
  • Use shadow/elevation only where needed to avoid clutter
  • Use SafeAreaView for proper spacing on iOS

Custom Fonts and Icons

You can use libraries like react-native-vector-icons and Google Fonts for custom header or tab styles:

npm install react-native-vector-icons

Example with an icon in the header:

<Stack.Screen
  name="Home"
  component={HomeScreen}
  options={{
    title: 'Dashboard',
    headerRight: () => (
      <Ionicons name="notifications-outline" size={24} color="#fff" style={{ marginRight: 15 }} />
    ),
  }}
/>


Conclusion

In this comprehensive guide, you learned how to master navigation in React Native using @react-navigation with Stack, Tabs, and Drawer navigators. We went beyond basic routing by adding deep linking, custom animations, and styling, giving you the tools to build smooth and intuitive navigation experiences that align with your app’s design.

With this solid foundation, you’re ready to scale your app with authentication flows, protected routes, and advanced navigation patterns. Whether you're building a simple app or a complex multi-screen experience, React Navigation provides all the flexibility and performance you need.

Next, consider implementing authentication flows with Firebase and React Navigation to manage login and protected screens.

You can find the full source code on our GitHub.

That's just the basics. If you need more deep learning about React.js, React Native, or related, you can take the following cheap course:

Thanks!