Navigating Heights: Material Top Tabs and Gorhom Bottom Sheet with React Navigation

Jan 29, 2024

Navigating Heights: Material Top Tabs and Gorhom Bottom Sheet with React Navigation

Our latest article explores how to streamline navigation using Material Top Tabs and elevate user experience with Gorhom Bottom Sheet.

Author

Sonali Mandal
Sonali MandalSoftware Engineer III

When we build screens in React Native, dealing with many tabs can get tricky, especially when they have sub-tabs, which have lists tied to the chosen tab and sub-tab values. Things get even more confusing if each list item includes a card that, when clicked, opens a modal with the same tabs and sub-tabs. It can be a bit of a puzzle.

Sub-tabs in React Native Screens
Sub-tabs in React Native Screens

But don't worry! Here's a straightforward and easy-to-use solution to keep everything organized.

In this article, we will see how we can achieve complex tab structure with minimal effort. Let’s get started!


Using Material Top Tabs Navigator

Material Top Tabs Navigator is a part of the React Navigation library, a popular navigation solution in the React Native ecosystem. This navigator specifically focuses on creating a tab-based navigation structure with tabs at the top of the screen. It aligns with the Material Design guidelines, providing a modern and aesthetically pleasing navigation experience.


Installation

To implement the tabs, ensure React Navigation is installed in your project. If not, you can install it using the following steps:

npm install @react-navigation/native 
 or
yarn add @react-navigation/native

Additionally, you'll need to install the Material Top Tabs Navigator:

npm install @react-navigation/material-top-tabs react-native-tab-view
 or
yarn add @react-navigation/material-top-tabs react-native-tab-view

Then, you need to install react-native-pager-view which is required by the navigator.

npm install react-native-pager-view
 or
yarn add react-native-pager-view


Creating Tab Array

const TabSubTabData = [
  {
    id: 1,
    title: 'Tab 1',
    subTabs: [
      { id: 1, title: 'SubTab 1', parentTab: 'Tab 1', screenName: 'Screen1' },
      { id: 2, title: 'SubTab 2', parentTab: 'Tab 1', screenName: 'Screen2' },
    ],
    screen: 'Screen1',
  },
  {
    id: 2,
    title: 'Tab 2',
    subTabs: [
      { id: 3, title: 'SubTab 1', parentTab: 'Tab 2', screenName: 'Screen3' },
      { id: 4, title: 'SubTab 2', parentTab: 'Tab 2', screenName: 'Screen4' },
    ],
    screen: 'Screen3',
  },
  // Add more objects as needed
]

Each tab in the array has its own array of sub-tabs.

Let’s use this array to create material top tabs in our TabNavigator component.

To use the tab navigator, import it from @react-navigation/material-top-tabs.

import { createMaterialTopTabNavigator } from '@react-navigation/material-top-tabs'

const Tab = createMaterialTopTabNavigator()

export const MyTabs() {
 return (
   <Tab.Navigator>
   {TabSubTabData.map((item, index) => (
              <Tab.Screen
                key={index}
                name={item.screen}
                options={{
                  title: `${item.title}`,
                }}
              >
                {(props) => (
                  <TabBody
                    currentTab={item}
                  />
                )}
              </Tab.Screen>
            ))}
   </Tab.Navigator>
 )
}

The above code sets up a top tab navigator MyTabs using the createMaterialTopTabNavigator function. It dynamically creates tab screens based on the data in the TabSubTabData array, each associated with a common screen component (TabBody) and configured with a title.

Now that we have created our tabs, let’s create our TabBody component. This component will have a list, and each list will show a card with brief information on it.

To enable a detailed view, we'll make these cards pressable, triggering the opening of a modal that mirrors the structure of tabs and sub-tabs.

import { FlashList } from '@shopify/flash-list'

export const TabBody({currentTab}) {

 const [subStatus, setSubstatus]=useState(currentTab.subTabs[0])

 const onPressSubTab = (subTab) => {
  setSubStatus(subTab)
 }

 const onPressCard = (card) => {
 // open the modal with detailed view
 }
 
 return (
   <>
   {currentTab.subTabs.map((subTab) => (
    <Button
     onPress={() => onPressSubTab(subTab)}
     title={subTab.title} />
   ))}
   <FlashList
     //list fetched from API based on Tab and subTab selection
     data={list}
     renderItem={({item}) => (
     <Pressable onPress={() => onPressCard(item)}>
     // UI to show a card as each item of the list.
     </Pressable>
     )}
   />
   </>
 )
}

Upon modal activation, it should promptly navigate to the tab corresponding to the card's initial position, with the preselected sub-status.

Various methods exist for modal implementation, and we have opted for the BottomSheet from gorhom/bottom-sheet. This choice is attributed to its seamless integration with React Navigation, along with robust keyboard handling capabilities.


Elevating User Experience with Gorhom Bottom Sheet

Gorhom Bottom Sheet is an open-source library for React Native that facilitates the creation of bottom sheets – a user interface element that slides up from the bottom of the screen.

Unlike traditional bottom sheets, Gorhom Bottom Sheet supports multi-level hierarchies. This means you can nest bottom sheets within each other, providing a layered and organized structure for presenting information or actions.

Why Gorhom Bottom Sheet?

  • Seamless React Navigation Integration: Gorhom Bottom Sheet is designed to seamlessly integrate with React Navigation, allowing for smooth navigation transitions and a consistent user experience. This compatibility is particularly advantageous as it allows our modal to be seamlessly opened from any screen, yielding optimal results.
  • Multi-Level Bottom Sheets: This library excels in supporting multi-level bottom sheets, empowering developers to create hierarchies for intricate user interfaces. In the context of our modal, this feature becomes especially valuable, enabling the implementation of more complex functionalities. For instance, having distinct buttons that trigger different modals is made feasible, and the ability to nest modals with each other is seamlessly supported by this library. 


Installation

npm install @gorhom/bottom-sheet
# or
yarn add @gorhom/bottom-sheet

This library needs these dependencies to be installed in your project before you can use it:

yarn add react-native-reanimated react-native-gesture-handler

Note: React Native Gesture Handler needs extra steps to finalize its installation. Please make sure to wrap your App with GestureHandlerRootView when you've upgraded to React Native Gesture Handler ^2. React Native Reanimated v2 needs extra steps to finalize its installation, please follow their installation instructions.

Let’s create our modal component.

import BottomSheet from '@gorhom/bottom-sheet'


const DetailSheetTab = createMaterialTopTabNavigator()

export const MyDetailSheet({route, navigation}) {

const TabSubtab = route.params  // to set initial tab & subtab

const bottomSheetRef = useRef<BottomSheet>(null)
const snapPoints = usememo(() => [1, ‘90%’], [])

const handleSheetChanges = useCallback((index: number) => {
 if (index === -1) {
    navigation.goBack()  // close modal
   }
}, [])

const initialRoute = useMemo(() => TabSubTabData.find((tab) => TabSubtab.TabId === tab.id), [TabSubtab])

const subStatus = useMemo(() => initialRoute.find((subtab) => TabSubtab.SubTabId === subtab.id), [initialRoute, TabSubtab])

return (
   <BottomSheet
    ref={bottomSheetRef}
    index={1}
    snapPoints={snapPoints}
    onChange={handleSheetChanges}
   >
   <DetailSheetTab.Navigator
    initialRouteName={initialRoute.screen}>
   {TabSubTabData.map((item, index) => (
              <DetailSheetTab.Screen
                key={index}
                name={item.screen}
                options={{
                  title: `${item.title}`,
                }}
              >
                {(props) => (
                  <DetailedWrapperComponent
                    currentTab={item}
                    subStatus={subStatus}
                  />
                )}
              </DetailSheetTab.Screen>
            ))}
   </DetailSheetTab.Navigator>
   </BottomSheet>
 )
}

Let's break down the key components of the code:

const TabSubtab = route.params: Extracts parameters from the route, which are related to tab and subtab information. We will use this to set the initial route of the navigator and preselect subtab as soon as our modal opens.

initialRoute: Determines the tab that should be focused when modal opens.

subStatus: Determines the subtab that should be preselected when modal opens.

Let’s leverage the most interesting feature of gorhom bottom sheet - react navigation support.

To open the modal, we just need to navigate to it just like we would navigate to any other screen in a stack navigator.

Add below code in the onPressCard function in the TabBody component:

const onPressCard = (card) => {
 navigation.navigate(‘MyDetailSheet’, {
  TabId: card.Status
  SubTabId: card.subStatus
 }
 }
}

Pass the selected tab and subtab as parameters when navigating to MyDetailSheet (modal). These parameters will serve as the initial route and preselected subtab, respectively, when the modal opens.


Redirection from Notification

Implementing our modal in this manner provides an additional benefit – seamless handling of notification redirection. For example, when a notification carries appended tab and sub-tab IDs in its URL, we can effortlessly extract these IDs. Upon navigating to 'MyDetailSheet' (our modal) and passing these extracted IDs as parameters, the bottom sheet will promptly open upon tapping the notification. This ensures proper redirection, with the required tab being focused and the associated sub-tab preselected.


Conclusion

Combining Material Top Tabs and Gorhom Bottom Sheet in a React Navigation setup provides a powerful and flexible navigation experience. Whether you're organizing content with top tabs or introducing interactive bottom sheets, this approach enhances user engagement and creates a modern and dynamic app interface.

Subscribe to Our Newsletter

More from the engineering frontline.

Dive deep into our research and insights on design, development, and the impact of various trends to businesses.
Insight
Building a Real-Time Photo Sync Video Experience Using 100ms SDK and Reactive State Management
Oct 15, 2026

Building a Real-Time Photo Sync Video Experience Using 100ms SDK and Reactive State Management

This blog explains how to build a real-time video experience with synchronized photo sharing, chat, and reactive state management using the 100ms SDK.

Insight
Taming Elasticsearch at Scale
Oct 13, 2026

Taming Elasticsearch at Scale

Optimize Elasticsearch for millions of records with better pagination, leaner payloads, efficient filtering, faster ingestion, and frontend techniques that keep search responsive with increase in data.

Insight
Deep Linking for Mobile Apps(React Native) with NestJs, AWS CloudFront, S3 & ALB: A Complete Guide
Oct 13, 2026

Deep Linking for Mobile Apps(React Native) with NestJs, AWS CloudFront, S3 & ALB: A Complete Guide

Build a self-hosted deep linking and referral system for React Native without a third-party SDK. This guide covers Android App Links, iOS Universal Links, an S3 and CloudFront setup, and a NestJS backend that routes users to the right app store.

Insight
How We Engineered a Three-Path Reward Processing System for a Real-Time Sports Platform
Oct 12, 2026

How We Engineered a Three-Path Reward Processing System for a Real-Time Sports Platform

This blog explains how a real-time sports platform uses synchronous, asynchronous, and scheduled processing paths to handle rewards without slowing down the player experience.

Insight
Building an MCP-Powered QA Agent
Oct 12, 2026

Building an MCP-Powered QA Agent

Build an MCP QA agent that runs Playwright tests, validates APIs, and analyzes failures through a controlled, auditable tool layer with human approval.

Insight
From Test Failure to Root Cause: Building an AI-Assisted Playwright Failure Analysis Pipeline
Oct 9, 2026

From Test Failure to Root Cause: Building an AI-Assisted Playwright Failure Analysis Pipeline

This blog explains how an AI-assisted Playwright pipeline can analyze test failure evidence, identify probable root causes, and support human-validated debugging.

Insight
The Model Context Protocol: From First Call to Production
Oct 8, 2026

The Model Context Protocol: From First Call to Production

This blog explains how Model Context Protocol (MCP) works, from tool discovery and execution to OAuth authorization, security controls, and production deployment.

Footer

The Right Conversation Can

Save You Six Months.

Book a Call