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-collapsible-tabs-reanimated

v0.2.0

Published

Collapsible tab views for React Native powered by Reanimated and Gesture Handler.

Downloads

26

Readme

react-native-collapsible-tabs-reanimated

Collapsible tab views for React Native powered by Reanimated, Gesture Handler, Pager View, and Worklets.

Use this library when you need a screen with:

  • A static header that collapses away as content scrolls.
  • A sticky tab bar that remains visible.
  • Horizontal pager tabs with independent vertical scroll positions.
  • Reanimated tab indicators, including underline and segmented-button styles.
  • Optional custom pull-to-refresh from the header.
  • Optional FlashList and LegendList wrappers.

Demo

Demo

Installation

npm install react-native-collapsible-tabs-reanimated react-native-reanimated react-native-gesture-handler react-native-pager-view react-native-worklets

If you use an optional list adapter, install its peer dependency too:

npm install @shopify/flash-list
npm install @legendapp/list

Peer Dependency Versions

| Package | Version | | --- | --- | | react | >=18 | | react-native | >=0.72 | | react-native-reanimated | >=4.0.0 | | react-native-gesture-handler | >=2.0.0 | | react-native-pager-view | >=6.0.0 | | react-native-worklets | >=0.6.0 | | @shopify/flash-list (optional) | >=2.0.0 | | @legendapp/list (optional) | >=3.0.0 |

Required Setup

Configure Reanimated and Gesture Handler in your app before using this package.

// Usually at the app entry point
import "react-native-gesture-handler";

Make sure Reanimated is configured in Babel according to the Reanimated installation guide.

Quick Start

import { useCallback } from "react";
import { Text, View } from "react-native";
import CollapsibleTabs from "react-native-collapsible-tabs-reanimated";

const data = Array.from({ length: 40 }, (_, index) => ({ id: String(index) }));

export function PortfolioScreen() {
  const renderItem = useCallback(
    ({ item }: { item: { id: string } }) => (
      <View style={{ padding: 16 }}>
        <Text>Row {item.id}</Text>
      </View>
    ),
    [],
  );

  return (
    <CollapsibleTabs.Root
      pageLength={2}
      initialStaticHeight={220}
      initialStickyHeight={56}
    >
      <CollapsibleTabs.Header>
        <CollapsibleTabs.StaticHeader>
          <View style={{ height: 220, justifyContent: "flex-end", padding: 24 }}>
            <Text style={{ fontSize: 28, fontWeight: "800" }}>Portfolio</Text>
          </View>
        </CollapsibleTabs.StaticHeader>

        <CollapsibleTabs.StickyHeader>
          <CollapsibleTabs.Bar>
            <CollapsibleTabs.Button index={0} name="Assets">
              Assets
            </CollapsibleTabs.Button>
            <CollapsibleTabs.Button index={1} name="Activity">
              Activity
            </CollapsibleTabs.Button>
            <CollapsibleTabs.MaterialIndicator />
          </CollapsibleTabs.Bar>
        </CollapsibleTabs.StickyHeader>
      </CollapsibleTabs.Header>

      <CollapsibleTabs.Pager>
        <CollapsibleTabs.Tab index={0} lazy={false}>
          <CollapsibleTabs.List
            data={data}
            keyExtractor={(item) => item.id}
            renderItem={renderItem}
          />
        </CollapsibleTabs.Tab>

        <CollapsibleTabs.Tab index={1} lazy={false}>
          <CollapsibleTabs.ScrollView contentContainerStyle={{ padding: 16 }}>
            <Text>Activity content</Text>
          </CollapsibleTabs.ScrollView>
        </CollapsibleTabs.Tab>
      </CollapsibleTabs.Pager>
    </CollapsibleTabs.Root>
  );
}

Component Anatomy

Use the components in this order:

<CollapsibleTabs.Root pageLength={numberOfTabs}>
  <CollapsibleTabs.Header>
    <CollapsibleTabs.StaticHeader>{/* collapses */}</CollapsibleTabs.StaticHeader>
    <CollapsibleTabs.StickyHeader>{/* stays visible */}</CollapsibleTabs.StickyHeader>
  </CollapsibleTabs.Header>

  <CollapsibleTabs.Pager>
    <CollapsibleTabs.Tab index={0}>{/* scrollable */}</CollapsibleTabs.Tab>
    <CollapsibleTabs.Tab index={1}>{/* scrollable */}</CollapsibleTabs.Tab>
  </CollapsibleTabs.Pager>
</CollapsibleTabs.Root>

Root

Root owns shared scroll state, pager progress, header heights, and optional refresh state.

Important props:

  • pageLength: required. Must match the number of tabs.
  • initialStaticHeight: recommended to reduce first-render flicker.
  • initialStickyHeight: recommended to reduce first-render flicker.
  • offsetAdjustment: use when the header should stop before fully collapsing, such as under a native status bar or custom inset.
  • refreshing, onRefresh: enable header pull-to-refresh.

Header

Header wraps the collapsible and sticky header content. It also handles direct drag gestures on the header.

<CollapsibleTabs.Header>
  <CollapsibleTabs.StaticHeader>{/* hero */}</CollapsibleTabs.StaticHeader>
  <CollapsibleTabs.StickyHeader>{/* tab bar */}</CollapsibleTabs.StickyHeader>
</CollapsibleTabs.Header>

Pager And Tab

Pager renders the horizontal pages. Each Tab must receive a stable index matching the related button.

<CollapsibleTabs.Pager>
  <CollapsibleTabs.Tab index={0} lazy={false}>
    <CollapsibleTabs.List data={data} renderItem={renderItem} />
  </CollapsibleTabs.Tab>
</CollapsibleTabs.Pager>

Tab is lazy by default. Pass lazy={false} if a tab should mount immediately.

Tab Bar Styles

Underline Tabs

Use MaterialIndicator for a classic underline bar.

<CollapsibleTabs.Bar tabButtonsGap={12}>
  <CollapsibleTabs.Button index={0} name="Assets">Assets</CollapsibleTabs.Button>
  <CollapsibleTabs.Button index={1} name="Activity">Activity</CollapsibleTabs.Button>
  <CollapsibleTabs.MaterialIndicator color="#2563eb" />
</CollapsibleTabs.Bar>

Segmented Tabs

Use SegmentIndicator before the buttons and set variant="segment" on each button.

<CollapsibleTabs.Bar
  backgroundColor="#eef2ff"
  tabButtonsGap={4}
  scrollButtons
  scrollContainerStyle={{ paddingHorizontal: 8, paddingVertical: 6 }}
>
  <CollapsibleTabs.SegmentIndicator color="#ffffff" />
  <CollapsibleTabs.Button index={0} name="Assets" variant="segment">
    Assets
  </CollapsibleTabs.Button>
  <CollapsibleTabs.Button index={1} name="Allocation" variant="segment">
    Allocation
  </CollapsibleTabs.Button>
</CollapsibleTabs.Bar>

For a two-tab full-width segmented control, pass fullWidth to Bar and each Button.

<CollapsibleTabs.Bar fullWidth tabButtonsGap={4}>
  <CollapsibleTabs.SegmentIndicator />
  <CollapsibleTabs.Button fullWidth index={0} name="First" variant="segment">
    First
  </CollapsibleTabs.Button>
  <CollapsibleTabs.Button fullWidth index={1} name="Second" variant="segment">
    Second
  </CollapsibleTabs.Button>
</CollapsibleTabs.Bar>

Scrollable Content

Use one of the built-in scroll wrappers inside each Tab.

<CollapsibleTabs.Tab index={0}>
  <CollapsibleTabs.List data={data} renderItem={renderItem} />
</CollapsibleTabs.Tab>

<CollapsibleTabs.Tab index={1}>
  <CollapsibleTabs.ScrollView>
    <Text>Short-form content</Text>
  </CollapsibleTabs.ScrollView>
</CollapsibleTabs.Tab>

Use List for regular FlatList data and ScrollView for short composed content. Each tab keeps its own independent scroll offset.

Custom Pull To Refresh

Pull-to-refresh is driven by dragging the header while it is fully visible.

import Animated, { useAnimatedStyle } from "react-native-reanimated";

function RefreshIndicator({ refreshProgress, isRefreshing }) {
  const style = useAnimatedStyle(() => ({
    opacity: isRefreshing.value ? 1 : refreshProgress.value,
    transform: [{ scale: 0.8 + refreshProgress.value * 0.2 }],
  }));

  return <Animated.View style={[{ width: 32, height: 32, borderRadius: 16 }, style]} />;
}

<CollapsibleTabs.Root refreshing={refreshing} onRefresh={reload} pageLength={2}>
  <CollapsibleTabs.Header
    renderRefreshControl={(info) => <RefreshIndicator {...info} />}
  >
    {/* header content */}
  </CollapsibleTabs.Header>
</CollapsibleTabs.Root>;

Refresh props on Root:

  • refreshing: controlled refreshing state.
  • onRefresh: called when the pull distance reaches the threshold.
  • refreshTriggerDistance: default 72.
  • refreshHoldDistance: default 56.
  • maxRefreshPullDistance: default 140.

The refresh context is also exported as useCollapsibleTabsRefreshContext for advanced custom UI.

FlashList

CollapsibleFlashList is available from a separate entry point so @shopify/flash-list remains optional.

import { CollapsibleFlashList } from "react-native-collapsible-tabs-reanimated/flash-list";

<CollapsibleTabs.Tab index={0}>
  <CollapsibleFlashList
    data={data}
    keyExtractor={(item) => item.id}
    renderItem={renderItem}
    estimatedItemSize={72}
  />
</CollapsibleTabs.Tab>;

Install the peer dependency first:

npm install @shopify/flash-list

LegendList

CollapsibleLegendList is available from a separate entry point so @legendapp/list remains optional.

import { CollapsibleLegendList } from "react-native-collapsible-tabs-reanimated/legend-list";

<CollapsibleTabs.Tab index={1}>
  <CollapsibleLegendList
    data={data}
    keyExtractor={(item) => item.id}
    renderItem={renderItem}
    estimatedItemSize={72}
    recycleItems
  />
</CollapsibleTabs.Tab>;

Install the peer dependency first:

npm install @legendapp/list

Imperative API

Attach a ref to Root to programmatically control the tab view.

const ref = useRef<CollapsibleTabsRootRef>(null);

<CollapsibleTabs.Root ref={ref} pageLength={2} />;

scrollToViewTop(animated?)

Scrolls the active tab list back to the top and reveals the collapsed header.

ref.current?.scrollToViewTop();        // animated (default)
ref.current?.scrollToViewTop(false);   // instant

setPage(page, animated?)

Programmatically navigates to a tab by its zero-based index.

ref.current?.setPage(1);        // switch to tab 1, animated (default)
ref.current?.setPage(0, false); // switch to tab 0, no animation

Example App

The example folder contains a runnable Expo SDK 56 app demonstrating:

  • ScrollView
  • List
  • CollapsibleFlashList
  • CollapsibleLegendList
  • Underline and segmented tab bars
  • Custom header pull-to-refresh
npm install
cd example
npx expo start

Press a to open Android, i to open iOS, or scan the QR code with Expo Go.

Notes

  • The package is unstyled by default. Colors, labels, indicators, refresh controls, and placeholders are configured through props.
  • @shopify/flash-list and @legendapp/list are optional peer dependencies. Install only what you use.
  • initialStaticHeight and initialStickyHeight are strongly recommended for smoother first render.
  • The tab bar button index, tab index, and Root.pageLength must stay in sync.