DEV Community

Ajmal Hasan
Ajmal Hasan

Posted on

Implementing Custom Tab Navigation with React Native

Navigating through different screens is essential in mobile apps. In this guide, we’ll implement a custom tab navigation system in a React Native app using @react-navigation/bottom-tabs. We’ll set up tab screens, create custom tab icons, and add animations for a smooth user experience.

Setting Up Tab Navigation

To get started, install the required navigation libraries:

yarn add @react-navigation/native react-native-screens react-native-safe-area-context @react-navigation/native-stack @react-navigation/bottom-tabs && cd ios && pod install && cd ..
Enter fullscreen mode Exit fullscreen mode

Defining the Navigation Stack

Our Navigation component will manage transitions between screens in the app. It’s the entry point of the app, where we define the main stack navigator.

Navigation.js

import HomeScreen from '@features/home/HomeScreen'
import IntroScreen from '@features/intro/IntroScreen'
import MainTabs from '@features/tabs/MainTabs'
import { NavigationContainer } from '@react-navigation/native'
import { createNativeStackNavigator } from '@react-navigation/native-stack'
import { navigationRef } from '@utils/NavigationUtils'

const Stack = createNativeStackNavigator()

const Navigation = () => {
    return (
        <NavigationContainer ref={navigationRef}>
            <Stack.Navigator initialRouteName='IntroScreen' screenOptions={{ headerShown: false }}>
                <Stack.Screen name='IntroScreen' component={IntroScreen} />
                <Stack.Screen options={{ animation: 'fade' }} name='HomeScreen' component={HomeScreen} />
                <Stack.Screen options={{ animation: 'fade' }} name='MainTabs' component={MainTabs} />
            </Stack.Navigator>
        </NavigationContainer>
    )
}

export default Navigation
Enter fullscreen mode Exit fullscreen mode

In this stack navigator:

  • IntroScreen: The introductory screen of the app.
  • HomeScreen: The main screen after logging in.
  • MainTabs: Contains the primary app tabs with custom navigation components.

Setting Up Tab Screens

Now let’s set up our main tabs, including custom icons and animations for smooth tab transitions.

MainTabs.js

import OrderScreen from '@features/order/OrderScreen'
import ProfileScreen from '@features/profile/ProfileScreen'
import ActivityScreen from '@features/activity/ActivityScreen'
import { createBottomTabNavigator } from '@react-navigation/bottom-tabs'
import CustomTabBar from './CustomTabBar'

const Tab = createBottomTabNavigator()

const MainTabs = () => {
    return (
        <Tab.Navigator
            tabBar={(props) => <CustomTabBar {...props} />}
            screenOptions={{
                headerShown: false,
                tabBarHideOnKeyboard: true
            }}
        >
            <Tab.Screen name='Order' component={OrderScreen} />
            <Tab.Screen name='Profile' component={ProfileScreen} />
            <Tab.Screen name='Activity' component={ActivityScreen} />
        </Tab.Navigator>
    )
}

export default MainTabs
Enter fullscreen mode Exit fullscreen mode

Here:

  • Order, Profile, and Activity represent different screens for each tab.
  • CustomTabBar controls the tab bar’s appearance.

Customizing the Tab Bar

Creating a custom tab bar allows for personalized icons and animations. Here’s how we implement our custom tab bar:

CustomTabBar.js

import { View, TouchableOpacity, Image, Linking } from 'react-native'
import React from 'react'
import Animated, { useAnimatedStyle, withTiming } from 'react-native-reanimated'
import { OrderTabIcon, ProfileTabIcon, ActivityTabIcon } from './TabIcon'
import { screenWidth } from '@utils/Constants'

const CustomTabBar = (props) => {
    const { state, navigation } = props

    // Animated style for sliding indicator
    const indicatorStyle = useAnimatedStyle(() => {
        const tabWidth = screenWidth / state.routes.length
        return {
            transform: [{ translateX: withTiming(state.index * tabWidth, { duration: 300 }) }],
            width: tabWidth,
            height: 3,
            backgroundColor: 'blue',
        }
    })

    return (
        <View style={styles.tabBarContainer}>
            <View style={styles.tabContainer}>
                {state.routes.map((route, index) => {
                    const isFocused = state.index === index

                    const onPress = () => {
                        const event = navigation.emit({
                            type: 'tabPress',
                            target: route.key,
                            canPreventDefault: true
                        })

                        if (!isFocused && !event.defaultPrevented) {
                            navigation.navigate(route.name)
                        }
                    }

                    return (
                        <TouchableOpacity
                            key={index}
                            onPress={onPress}
                            style={[styles.tabItem, isFocused && styles.focusedTabItem]}
                        >
                            {route.name === 'Order' && <OrderTabIcon focused={isFocused} />}
                            {route.name === 'Profile' && <ProfileTabIcon focused={isFocused} />}
                            {route.name === 'Activity' && <ActivityTabIcon focused={isFocused} />}
                        </TouchableOpacity>
                    )
                })}
            </View>
            {/* Sliding indicator */}
            <Animated.View style={[styles.slidingIndicator, indicatorStyle]} />
        </View>
    )
}

export default CustomTabBar
Enter fullscreen mode Exit fullscreen mode

In this component:

  • Each tab button (TouchableOpacity) navigates to the corresponding screen on press.
  • The sliding indicator (indicatorStyle) animates horizontally, moving with each tab.
  • We also add an external link button for additional functionality.

Adding Custom Tab Icons

Custom icons give a unique look to each tab. Here’s how we define icons for each tab:

TabIcon.js

import OrderIcon from '@assets/icons/order.png'
import ProfileIcon from '@assets/icons/profile.png'
import ActivityIcon from '@assets/icons/activity.png'
import { Image } from 'react-native'
import React from 'react'

export const OrderTabIcon = ({ focused }) => (
    <Image source={OrderIcon} style={{ tintColor: focused ? 'blue' : 'gray' }} />
)

export const ProfileTabIcon = ({ focused }) => (
    <Image source={ProfileIcon} style={{ tintColor: focused ? 'blue' : 'gray' }} />
)

export const ActivityTabIcon = ({ focused }) => (
    <Image source={ActivityIcon} style={{ tintColor: focused ? 'blue' : 'gray' }} />
)
Enter fullscreen mode Exit fullscreen mode

Each icon changes color based on whether it is selected, providing visual feedback to the user.

Styles

Here’s a simple style setup for the custom tab bar and indicator.

styles.js

import { StyleSheet } from 'react-native'

export const styles = StyleSheet.create({
    tabBarContainer: {
        flexDirection: 'row',
        alignItems: 'center',
        justifyContent: 'space-between',
        paddingVertical: 10,
        backgroundColor: 'white',
        position: 'absolute',
        bottom: 0,
        width: '100%',
        borderTopWidth: 1,
        borderTopColor: '#ccc',
    },
    tabContainer: {
        flexDirection: 'row',
        alignItems: 'center',
        flex: 1,
    },
    tabItem: {
        flex: 1,
        alignItems: 'center',
        justifyContent: 'center',
        paddingVertical: 10,
    },
    focusedTabItem: {
        color: 'blue',
    },
    slidingIndicator: {
        position: 'absolute',
        bottom: 0,
    },
    externalLink: {
        position: 'absolute',
        right: 20,
        bottom: 10,
    },
    icon: {
        width: 24,
        height: 24,
    }
})
Enter fullscreen mode Exit fullscreen mode

Conclusion

Using React Native’s navigation libraries, you can create a customized tab navigation system with animations, icons, and additional actions. By incorporating animations and unique icons, you create a polished user experience that enhances the app’s functionality and visual appeal.

Top comments (0)