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-scroll-coordinator

v0.1.0-alpha.0

Published

Cross-platform scroll coordinator for collapsible headers, sticky tabs, pagers, and virtualized lists.

Readme

react-native-scroll-coordinator

Cross-platform React Native scroll coordination primitives for a collapsible header, sticky tab bar, horizontal pager, and one virtualized vertical list per page. The API is not tied to a home screen: business code uses one typed configuration while each platform keeps its appropriate scroll engine.

0.1.0-alpha.0 is intended for production evaluation, not an unconditional 1.0 compatibility promise. Android owns the complete native coordination path. iOS and Web adapters remain experimental while their real-device acceptance matrices are completed; see docs/COMPATIBILITY.md.

Architecture

The Android hierarchy uses the Material Components contract directly:

CoordinatorLayout
├── AppBarLayout
│   ├── header: SCROLL | EXIT_UNTIL_COLLAPSED | optional SNAP
│   └── tab bar: sticky, no scroll flags
└── content: AppBarLayout.ScrollingViewBehavior
    └── PagerView
        └── FlatList / ScrollView per page

JavaScript reports header and tab-bar heights only when React Native layout changes. Android owns nested scrolling, fling continuation, collapse limits, sticky placement, and snap on the UI thread. No per-frame scroll values cross the JS/native boundary.

Installation

yarn add react-native-scroll-coordinator \
  react-native-pager-view react-native-collapsible-tab-view \
  react-native-reanimated

The alpha is verified against React Native 0.81 with the New Architecture and Hermes enabled. Android autolinking registers the Fabric component descriptor and native view managers. Later React Native versions are intentionally outside the peer range until the compatibility CI and device suite pass.

FlashList is optional. Install it only when using the adapter subpath:

yarn add @shopify/flash-list

For local workspace development, add it as a portal dependency pointing to:

../react-native-scroll-coordinator

Cross-platform usage

import { CoordinatorTabs } from 'react-native-scroll-coordinator';

<CoordinatorTabs
  tabs={[
    { key: 'wallet', label: 'Wallet', render: () => <WalletPage /> },
    { key: 'history', label: 'History', render: () => <HistoryPage /> },
  ]}
  renderHeader={() => <Header />}
  renderTabBar={({ activeKey, selectTab, tabs }) => (
    <TabBar activeKey={activeKey} onSelect={selectTab} tabs={tabs} />
  )}
  minimumHeaderHeight={72}
  snapEnabled
/>;

Tab keys must be unique and stable. Selection is reconciled by key when tabs are reordered; removing the active uncontrolled tab falls back to the first remaining tab. An unknown controlled key and duplicate keys throw explicit configuration errors.

CoordinatorHeader uses role-based slots so geometry, content transitions, and background effects can be combined independently:

import {
  CoordinatorHeader,
  CoordinatorTabs,
} from 'react-native-scroll-coordinator';

<CoordinatorTabs
  renderHeader={() => (
    <CoordinatorHeader transitionEnd={0.85} transitionStart={0.55}>
      <CoordinatorHeader.Background parallaxRate={0.5}>
        <HeaderImage />
      </CoordinatorHeader.Background>
      <CoordinatorHeader.Expanded height={240}>
        <HeaderContent />
      </CoordinatorHeader.Expanded>
      <CoordinatorHeader.Collapsed height={64}>
        <CompactHeaderContent />
      </CoordinatorHeader.Collapsed>
    </CoordinatorHeader>
  )}
  // ...
/>;

Expanded determines the open height. When Collapsed exists, its explicit or measured height automatically becomes the retained height; it does not need to be repeated on CoordinatorTabs. Omitting Collapsed makes the Header hide completely while the TabBar remains sticky. Heights are optional when the slot content has an intrinsic layout, and explicit values avoid a first-layout measurement update.

Background does not participate in geometry. Its parallaxRate is clamped to 0...1 and defaults to 0.5. Android applies parallax and cross-fade from the native AppBar offset; iOS uses the collapsible-header SharedValue on the UI thread. Neither path sends per-frame offsets through JavaScript. Web currently keeps the expanded layout with standard header movement.

The previous flat background, children, and collapsed props remain available for compatibility, and CoordinatorHeader.Container remains an alias for CoordinatorHeader. The root compound-component syntax above is recommended.

Use CoordinatorFlatList for a flat virtualized list or CoordinatorSectionList when the page needs grouped rows and section headers:

import { CoordinatorSectionList } from 'react-native-scroll-coordinator';

<CoordinatorSectionList
  sections={sections}
  keyExtractor={(item) => item.id}
  renderItem={renderItem}
  renderSectionHeader={renderSectionHeader}
/>;

Both adapters preserve the native list API, including React 19's plain ref prop, refresh controls, pagination, and virtualization. For heavier cards, use the FlashList adapter:

import { CoordinatorFlashList } from 'react-native-scroll-coordinator/flash-list';

<CoordinatorFlashList
  data={items}
  keyExtractor={(item) => item.id}
  renderItem={renderItem}
/>;

On Android the adapter supplies CoordinatorScrollView through FlashList's renderScrollComponent; FlashList still owns recycling and item layout. On iOS it maps to Tabs.FlashList. On Web it injects the header and sticky tab bar into the virtualized list, so the list remains the only vertical scroll owner. Do not wrap any of these lists in another same-axis ScrollView.

For a custom virtual-list engine, use the generic adapter and make its physical scroll host the supplied renderScrollComponent:

import { CoordinatorVirtualList } from 'react-native-scroll-coordinator';

<CoordinatorVirtualList>
  {({ renderScrollComponent }) => (
    <CustomVirtualList
      data={items}
      renderItem={renderItem}
      renderScrollComponent={renderScrollComponent}
    />
  )}
</CoordinatorVirtualList>;

The custom engine may own windowing, recycling, measurement, and item reuse, but it must use the supplied component as its only vertical scroll owner and forward the normal scroll ref, events, content size, and refresh props. This is the same integration seam used by the FlashList adapter; FlashList itself is not required.

Wrap a nested horizontal scroller, such as a banner carousel, with CoordinatorHorizontal. The child consumes horizontal drags while it can scroll in that direction. At its first or last item, the drag is released to the outer tab pager. Vertical intent is released immediately to the page's vertical coordinator. On Android, CoordinatorHorizontal is only a native region marker: the root native coordinator performs hit testing, axis locking, directional edge queries, and pager arbitration on the UI thread before the event reaches ViewPager2. JavaScript receives no per-frame scroll or gesture events for this protocol.

import { CoordinatorHorizontal } from 'react-native-scroll-coordinator';
import { CoordinatorFlashList } from 'react-native-scroll-coordinator/flash-list';

<CoordinatorFlashList
  data={items}
  ListHeaderComponent={
    <CoordinatorHorizontal>
      <ScrollView horizontal nestedScrollEnabled>
        {banners.map(renderBanner)}
      </ScrollView>
    </CoordinatorHorizontal>
  }
/>;

Only horizontal regions that compete with the outer pager need this boundary; ordinary buttons and vertical list items require no extra wrapper.

On iOS, CoordinatorHorizontal is a Fabric component that creates native gesture-failure relationships between its descendant UIScrollView and the ancestor pager. A drag that begins while the banner can continue stays with the banner; a new drag that begins at its directional edge goes to the pager. UIKit cannot transfer an already-recognized pan from the middle of a banner to the third-party pager without replacing that pager, so uninterrupted same-finger mid-gesture handoff is not claimed on iOS.

On Web, the outer tabs use a native CSS scroll-snap container instead of a page-level PanResponder. Browser scroll chaining lets the nested banner keep the gesture until its directional edge and then offer it to the outer pager.

The common Android/iOS/Web configuration and platform invariants are in docs/CROSS_PLATFORM_CONTRACT.md.

Example

The runnable React Native app lives in example. It includes dynamic header height, sticky tab presses, horizontal pager gestures, nested banners on the Wallet and History pages, long virtualized lists, pull-to-refresh, and the continuous-fling acceptance case.

Run it with yarn workspace CoordinatorExample android, ios, or web.

Verification

yarn test
yarn typecheck
yarn build
yarn test:release
yarn test:tarball
yarn workspace CoordinatorExample build:web

# The standalone example owns the Gradle wrapper and autolinks this package:
./example/android/gradlew -p example/android \
  :react-native-scroll-coordinator:testDebugUnitTest \
  :app:assembleDebug

yarn check is the local release gate. Publishing also verifies the configured repository, homepage, and bugs URLs before creating a package.