Skip to content
New: windowConfig caps tab memory

Collapsible tabs that don't jump.

The smooth collapsible tab view for React Native — per-tab scroll memory, a jump-free header, and first-class FlashList & LegendList support.

Get started GitHub
  • Reanimated 3 & 4
  • Old & New Architecture
  • Expo (incl. Expo Go)
  • iOS & Android

Why it's different

Built to stay smooth

Every feature follows from one decision: decouple the header from tab scroll offsets. No force-scrolling, no sync hacks.

Jump-free header

The header position is its own animated value, driven only by the active tab’s scroll deltas. Switching tabs never moves it — no flicker, no blank space, no ghost header.

Per-tab scroll memory

Each tab keeps its own scroll offset, keyed by name. Switch away and back and you land exactly where you left — even across dynamic add/remove.

Every list, first-class

Drop-in adapters for FlatList, ScrollView, SectionList, FlashList v2 and LegendList — each pads, restores offsets, and feeds the collapse logic automatically.

Scrollable header, working buttons

A pan gesture on the header drives the active list (with release momentum) but only after 10px of movement — so taps still reach your buttons. The bug nobody else fixed.

Bounded tab memory

windowConfig caps how many tabs stay mounted — the rest free their native views via React’s <Activity> while keeping state and scroll position for instant restore. 60 tabs, only 3 live.

Runs on the UI thread

All animation runs on the UI thread via Reanimated — no shared-value reads during render, no Jest loops, no breakage on every Reanimated or Expo bump.

Works with every list — drop-in, same props:

FlatListScrollViewSectionListFlashList v2LegendList

Migrating?

The bugs you've been fighting, gone

The category’s classic bugs come from tying the header to tab scroll offsets and force-scrolling every tab to stay in sync. Decoupling them removes a whole class of issues by design.

react-native-collapsible-tab-view

Buttons in the header block scrolling (most-requested, never fixed)

Pan gesture + tap pass-through — both work

Blank space / ghost header on tab switch

Structurally impossible: header decoupled from offsets

Non-ASCII tab names break sync

`name` is pure identity; display text lives in `label`

Breaks on every Reanimated / Expo bump

Public APIs only, no render-time shared reads — v3 & v4

Jumping to a far tab mounts every tab in between

Only the destination mounts; intermediates stay placeholders

`onTabChange` fires for every intermediate tab

Fires once, for the settled destination

FlashList: header won’t collapse, short lists break

Dedicated v2 adapter with a real Animated.ScrollView

Dynamic tabs reset scroll positions

Offsets keyed by name survive add / remove

Quick start

Two components, that's the API

Wrap your tabs in a Container, give each a name, and use the drop-in scrollables. The header is measured for you.

1. Install

npm install react-native-collapsible-tab
# peer deps (you likely have them):
npx expo install react-native-reanimated \
  react-native-gesture-handler react-native-pager-view

3. Cap memory on big tab sets

<Tabs.Container windowConfig={{ ahead: 1, behind: 1 }}>
  {tabs}
</Tabs.Container>
// 60 tabs, but only the focused one ±1 stay mounted

2. Use it

import { Tabs } from 'react-native-collapsible-tab';

function Profile() {
  return (
    <Tabs.Container
      renderHeader={() => <ProfileHeader />}  // measured automatically
      minHeaderHeight={insets.top}            // stays visible when collapsed
      lazy                                    // mount tabs on first focus
      snapThreshold={0.5}                     // optional: snap open/closed
    >
      <Tabs.Tab name="posts" label="Posts">
        <Tabs.FlatList
          data={posts}
          renderItem={({ item }) => <Post post={item} />}
          keyExtractor={(item) => item.id}
        />
      </Tabs.Tab>
      <Tabs.Tab name="media" label="Media">
        <Tabs.ScrollView>
          <MediaGrid />
        </Tabs.ScrollView>
      </Tabs.Tab>
    </Tabs.Container>
  );
}

Ship tabs that feel native.

One install, two components, zero header jank.

$ npm install react-native-collapsible-tab