Skip to content

BouarourMohammed/react-native-synced-tab-view

Folders and files

NameName
Last commit message
Last commit date

Latest commit

Β 

History

5 Commits
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 

Repository files navigation

react-native-synced-tab-view

Synchronized top tabs with a measured, collapsible header for React Native. Build profile, feed, and dashboard layouts where horizontal paging and vertical scroll positions stay aligned across tabs.

tab-sync.mp4

⚑ Powered by Reanimated + Worklets

This library uses react-native-reanimated and react-native-worklets to keep tab switching, header collapse, and scroll synchronization smooth across modern React Native apps.

  • Smooth shared-value driven interactions: Active tab state and indicator movement are animated with Reanimated.
  • Synchronized scrolling: Header and tab content offsets stay coordinated when users swipe between tabs.
  • Modern compatibility: Built for current React Native projects using Reanimated >= 4.

✨ Features

  • Collapsible Header Layout: Render any header above your tabs and let the package measure and sync it automatically.
  • Synced Tab Content: Keep vertical scroll state aligned while switching between tab pages.
  • Ready-to-use Scroll Wrappers: Use TabFlatList, TabSectionList, and TabScrollView out of the box.
  • Custom List Support: Wrap third-party scrollers like FlashList or Legend List with createSyncedTabScrollComponent.
  • Customizable Top Tabs: Style the indicator, labels, colors, and even provide a fully custom renderTabItem.
  • Profile-style UI Friendly: Designed for layouts like social profiles, dashboards, and content hubs.

🧩 Compatibility

Target Supported Version
React Native >= 0.76
React >= 18
Reanimated >= 4
React Native Worklets >= 0.7.0
Android Supported
iOS Supported
Web Not supported yet

πŸ“¦ Installation

Install the package and its peer dependencies:

yarn add react-native-synced-tab-view react-native-reanimated react-native-worklets

Peer Dependency Setup

Make sure your project is already configured for react-native-reanimated. After installing dependencies, rebuild the native app. If you are using a bare iOS project, run:

cd ios && pod install

If you are using Expo, install versions compatible with your SDK and rebuild the app if needed.

πŸš€ Quick Start

import * as React from "react";
import {
  StyleSheet,
  Text,
  View,
  type ListRenderItemInfo,
} from "react-native";
import {
  SyncedTabsLayout,
  TabFlatList,
  type TabItem,
  type TabPageProps,
  type TabViewTopTabsProps,
} from "react-native-synced-tab-view";

type DemoItem = {
  id: string;
  title: string;
};

const POSTS_DATA: DemoItem[] = Array.from({ length: 12 }, (_, index) => ({
  id: `post-${index + 1}`,
  title: `Post ${index + 1}`,
}));

const SAVED_DATA: DemoItem[] = Array.from({ length: 12 }, (_, index) => ({
  id: `saved-${index + 1}`,
  title: `Saved ${index + 1}`,
}));

const keyExtractor = (item: DemoItem) => item.id;

const DemoCard = React.memo(({ item }: { item: DemoItem }) => (
  <View style={styles.card}>
    <Text style={styles.cardTitle}>{item.title}</Text>
  </View>
));

type FeedTabProps = Pick<TabPageProps, "index" | "width"> & {
  data: DemoItem[];
};

const FeedTab = React.memo(function FeedTab({
  index,
  width,
  data,
}: FeedTabProps) {
  const renderItem = React.useCallback(
    ({ item }: ListRenderItemInfo<DemoItem>) => <DemoCard item={item} />,
    [],
  );

  return (
    <TabFlatList
      index={index}
      width={width}
      data={data}
      keyExtractor={keyExtractor}
      contentContainerStyle={styles.listContent}
      renderItem={renderItem}
      showsVerticalScrollIndicator={false}
    />
  );
});

export default function ExampleScreen() {
  const tabs = React.useMemo(
    () =>
      [
        {
          key: "posts",
          name: "Posts",
          component: ({ index, width }: TabPageProps) => (
            <FeedTab index={index} width={width} data={POSTS_DATA} />
          ),
        },
        {
          key: "saved",
          name: "Saved",
          component: ({ index, width }: TabPageProps) => (
            <FeedTab index={index} width={width} data={SAVED_DATA} />
          ),
        },
      ] satisfies TabItem[],
    [],
  );

  return (
    <View style={styles.container}>
      <SyncedTabsLayout
        header={
          <View style={styles.header}>
            <Text style={styles.title}>Profile</Text>
            <Text style={styles.subtitle}>
              Collapsible header content goes here.
            </Text>
          </View>
        }
        tabs={tabs}
        initialTabIndex={0}
        topTabsProps={TOP_TABS_PROPS}
      />
    </View>
  );
}

const styles = StyleSheet.create({
  container: {
    flex: 1,
    backgroundColor: "#fff",
  },
  header: {
    padding: 24,
    backgroundColor: "#f3f4f6",
  },
  title: {
    fontSize: 24,
    fontWeight: "700",
    color: "#111827",
    marginBottom: 8,
  },
  subtitle: {
    fontSize: 14,
    color: "#4b5563",
  },
  listContent: {
    padding: 16,
  },
  card: {
    padding: 16,
    borderRadius: 12,
    backgroundColor: "#e5e7eb",
    marginBottom: 12,
  },
  cardTitle: {
    fontSize: 16,
    fontWeight: "600",
    color: "#111827",
  },
  tabText: {
    fontSize: 14,
    fontWeight: "600",
  },
});

const TOP_TABS_PROPS = {
  indicatorColor: "#111827",
  indicatorHeight: 3,
  tabTextStyle: styles.tabText,
} satisfies TabViewTopTabsProps;

βš™οΈ API Reference

Exports

Export Description
SyncedTabsLayout High-level layout component that wires the provider, measured header, top tabs, and pager together.
TabFlatList Synchronized FlatList wrapper for tab content.
TabSectionList Synchronized SectionList wrapper for tab content.
TabScrollView Synchronized ScrollView wrapper for tab content.
createSyncedTabScrollComponent Factory for wrapping third-party scrollable components.

SyncedTabsLayout Props

Prop Type Description Default
header React.ReactNode Content rendered above the tabs and measured by the shared scroller. Required
tabs TabItem[] Tab definitions shared between the tab bar and pager. Required
width number Width of the tab bar and page area. Screen width
initialTabIndex number Tab shown on first render. 0
topTabsProps TabViewTopTabsProps Props forwarded to the built-in top tabs component. {}
scrollerProps TabViewScrollerProps Props forwarded to the horizontal pager scroller. {}
tabsProviderProps object Shared scroll behavior options, including resetPreviousScrollingY. { resetPreviousScrollingY: true }
scrollerContainerProps object Props for the outer synchronized vertical scroller. β€”
headerContainerProps object Props for the measured header container. β€”

TabItem Shape

Field Type Description
key string | number Stable identity used for React keys and tab state when provided.
id string | number Alternate stable identity if key is not used.
name string Label rendered in the top tabs.
component (props: TabPageProps) => React.ReactNode Function that renders the tab page.

TabPageProps includes:

  • item: the current TabItem
  • index: the tab index
  • width: the page width
  • isActive: a Reanimated shared value indicating whether the tab is active

Synced Scroll Components

Component Based On Extra Props
TabFlatList<T> FlatListProps<T> index: number, width?: number
TabSectionList<ItemT, SectionT> SectionListProps<ItemT, SectionT> index: number, width?: number
TabScrollView ScrollViewProps index: number, width?: number

TabFlatList and TabSectionList intentionally do not expose windowSize.

Common topTabsProps Options

Prop Type Description Default
tabItemStyle StyleProp<ViewStyle> Style applied to each tab item wrapper. β€”
tabTextStyle StyleProp<TextStyle> Style applied to tab labels. β€”
indicatorColor string Color of the active indicator. "white"
indicatorHeight number Height of the indicator. 4
indicatorWidth number Width of the indicator. 50
showIndicator boolean Whether the indicator is visible. true
renderTabItem (props: TopTabItemRenderProps) => React.ReactElement Custom tab item renderer. β€”
renderIndicator () => React.ReactElement Custom indicator renderer. β€”

πŸ§ͺ Custom Scroll Components

If you want to sync a third-party list, wrap it with createSyncedTabScrollComponent:

import { FlashList } from "@shopify/flash-list";
import { createSyncedTabScrollComponent } from "react-native-synced-tab-view";

const SyncedFlashList = createSyncedTabScrollComponent(FlashList);

The wrapped component still forwards refs to the underlying list instance, so imperative methods remain available.

πŸ›  Why not wire this manually?

Profile-style tab layouts usually require several moving parts at the same time: a measured header, a horizontal pager, synchronized vertical offsets, and custom tab visuals. Wiring all of that by hand often leads to duplicated onScroll logic, ref coordination, and edge cases when switching tabs.

react-native-synced-tab-view packages those behaviors behind a small API so each tab can focus on rendering content instead of managing scroll synchronization.

πŸ“± Example App

The repository includes an Expo example app with these demo flows:

  • Base synchronized tabs example
  • Example with saved tab position
  • Instagram-style profile layout
  • TikTok-style profile layout
  • FlashList integration example
  • Legend List integration example

You can keep this section as-is or replace it with screenshots, GIFs, or hosted demo links later.

⚠️ Known Issues

  • Tab scroll-position reset and restore behavior should currently be considered experimental. On Android, it can flicker and may not be stable across every list adapter.
  • When using a LegendList adapter inside a tab together with position reset on tab switch (resetPreviousScrollingY: true), resetting the list can temporarily show a white background.

πŸ“Œ Limitations

  • This package currently supports Android and iOS. Web support is not available yet.
  • The top section does not currently support a horizontal FlatList.
  • If you need horizontal list content there, use a horizontal FlashList or horizontal LegendList instead.

πŸ’‘ Recommendations

  • For the best performance with a vertical list and tabsProviderProps={{ resetPreviousScrollingY: false }}, prefer LegendList.
  • If resetPreviousScrollingY is enabled (true, the default), prefer FlashList.

πŸ§ͺ Local Development

yarn install
yarn start
yarn example:ios
yarn example:android
yarn typecheck
yarn build

🧯 Troubleshooting

  • Animations are not working: Verify react-native-reanimated is installed and configured correctly in your app, then rebuild the native project.
  • Scroll sync feels inconsistent after tab changes: Provide a stable key or id on every TabItem, especially if tab order can change.
  • You need a different list implementation: Wrap it with createSyncedTabScrollComponent instead of syncing it manually.
  • You want the outer vertical scroll indicator visible: Pass scrollerContainerProps={{ showsVerticalScrollIndicator: true }}.

πŸ“„ License

MIT Β© Mohammed B.

β˜• Support

If you find this project useful and would like to support its ongoing development, consider buying me a coffee! Your support helps keep the creativity brewing and allows me to continue improving and maintaining this project. Thank you! β˜•πŸ’–

Buy Me A Coffee

About

πŸš€ Synchronized top tabs with a measured, collapsible header for React Native. Build profile, feed, and dashboard layouts where horizontal paging and vertical scroll positions stay aligned across tabs.

Resources

Stars

Watchers

Forks

Packages

 
 
 

Contributors