npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

react-native-synced-tab-view

v1.0.1

Published

A synchronized tab view with a collapsible header for React Native.

Readme

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.

react-native-synced-tab-view iOS demo

⚡ 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! ☕💖