
How to Create a Collapsing Tab Header Using React Native
Collapsing header and swipeable tabs are one of the most common UI elements in mobile apps. Such a pattern is widely used on profile screens on Social Media apps like Instagram or Twitter, for example.
In this article, we are going to create a screen with a collapsing header and multiple swipeable tabs below step-by-step using React Native. This behavior can be achieved easily with the help of React Native Reanimated and React Navigation libraries.
Starting point of creating React Native collapsible tab
This simple tab screen will be the starting point of our journey. Itās just 2 tabs created using material top tab navigator from React Navigation. Each tab contains a FlatList with some mocked data.
Nothing special about the implementation of the ConnectionList, except for a reference forwarding. Weāll need to use this technique in the future, so letās prepare it in advance.
Here is the simple Profile component with two tabs. It will be the canvas for our future React Native collapsible tabs.
Thatās what we have after completing the first step and rendering the component above.

Simple static header
To implement collapsing behavior, weāll need to place the header above our screen and add a corresponding offset to the list and tab components.
In order to provide a correct offset, weāll have to know the exact height of the header. If you already know it ā good for youNothing needs to be solved when we already know the height before rendering. But itās not the case, so this is where onLayout comes in handy.
This offset should be added to the tab bar component and the content containers of our lists. React Navigation provides an easy way to customize a tab bar appearance via tabBar prop.
Additionally, letās replace the SafeAreaView container with a plain View. Itās better to add the insets handling manually inside the content containers to avoid the list item cutting like in the first screenshot.
Okay, that will work, but what about the very first render? We still know nothing about the headerās height and onLayout callback hasnāt been called yet. To avoid the abruptness of the swipes caused by the unsuitable header height, weāll have to be a little bit creative.
The idea is to use relative rendering instead of absolute one, but only for the first render. The components will perfectly align below one another with no additional help. And as soon as we know the headerās height, weāll re-render it using absolute alignment. The component tree will be adjusted, but a user wonāt notice a thing.

Animating the Header
After weāve created a static header, itās time to spice it up with some animations.
React Native Reanimated has been recently upgraded to v2, which brings a brand new imperative animation API. At Stormotion, we always try to be up to date with the new technologies and approaches, so we definitely wanted to give it a go š
The point of animation here is to make more space for the main content by collapsing the header and making the tabs stick to the top as the user scrolls.
Before starting to work on animations, weād like to provide a simple way to determine the scroll length required to collapse the header.
In this case, header height diff is exactly equal to the scroll distance we need to use.
With React Native Reanimated, animated scroll value capturing became significantly easier.
Now, we need to create some variable which will reflect the animated scroll position of the currently displayed list. Unfortunately, tab navigator doesnāt provide any way to listen to current tab changes. However, we can achieve this by a little trick with a custom tab bar.
At this stage, we can finally animate the header.

As you can see, collapsing is working, but itās not exactly what we wanted ā an excess part of the header remains displayed and cropped. Our goal here is to display only a profile name when the header is collapsed.
Looks much better!

Here is the detailed overview of the dimensions used to create the collapsing animation.

Scroll syncing
This example already looks pretty good, but it isn't completely ready yet since we wanted to have more than 1 list. In this case, we have to synchronize the header position between the two tabs. Otherwise, the header will jump while switching the tab.

The idea is to manually set a correct offset to all the lists that are not displayed to sync them with the currently displayed tab.
Weāll need to create some entity which we will use to store the information about each list reference and its current position. Letās name it ScrollPair:
Here is where previously implemented ref forwarding comes in handy. This list of the so-called scroll pairs will help us to go through all the lists and sync the header position.
In fact, this concise hook is the key solution of the issue! In the sync function, we are iterating over the list of all scroll pairs and adjusting their scroll offset depending on the offset of the current list.
When applying the hook to the list, weād recommend using both onMomentumScrollEnd and onScrollEndDrag callbacks to cover all possible scrolling cases.
The last but not the least. Some lists, like our āFriendsā list are too short to perform long enough scroll in order to collapse the header. Therefore, we need to add a satisfying minHeight to the contentContainerStyle prop.
Thatās it, the screen is ready! We hope that this step-by-step guide was helpful for you!

You can find the complete source code in our GitHub repository.
If you have any questions, feel free to reach out to us. Weāll be happy to help you out.