Important: This documentation covers Yarn 1 (Classic).
For Yarn 2+ docs and migration guide, see yarnpkg.com.

Package detail

react-native-collapsible-tab-view

PedroBern113.7kMIT8.0.1TypeScript support: included

Collapsible tab view component for React Native

react-native-component, react-component, react-native, ios, android, tab, swipe, scrollable, coverflow

readme

React Native Collapsible Tab View

Build Status Version MIT License Runs with Expo

🚀 Version 6 released with Reanimated v3 support

React Native Collapsible Tab View is a versatile library for creating collapsible tab views using Reanimated.

  • Explore the examples for the source code of the Expo app.

Credits

The react-native-tab-view example app was used as a template for the demos.

Demo

Default Snap revealHeaderOnScroll revealHeaderOnScroll + Snap

Features

  • UI thread animations and interactions
  • High customizability
  • Full TypeScript support
  • Lazy loading with fade-in animation
  • DiffClamp header
  • Interpolated header
  • Scroll snap (with interpolated header)
  • Animated snap (with diffClamp header)
  • Scrollable tabs, inspired by the react-native-tab-view tab bar

Installation

To install the library, open a terminal in your project's root directory and run:

yarn add react-native-collapsible-tab-view react-native-pager-view

Then, add Reanimated, follow the official installation guide.

Quick Start

import React from 'react'
import { View, StyleSheet, ListRenderItem } from 'react-native'
import { Tabs } from 'react-native-collapsible-tab-view'

const HEADER_HEIGHT = 250

const DATA = [0, 1, 2, 3, 4]
const identity = (v: unknown): string => v + ''

const Header = () => {
  return <View style={styles.header} />
}

const Example: React.FC = () => {
  const renderItem: ListRenderItem<number> = React.useCallback(({ index }) => {
    return (
      <View style={[styles.box, index % 2 === 0 ? styles.boxB : styles.boxA]} />
    )
  }, [])

  return (
    <Tabs.Container
      renderHeader={Header}
      headerHeight={HEADER_HEIGHT} // optional
    >
      <Tabs.Tab name="A">
        <Tabs.FlatList
          data={DATA}
          renderItem={renderItem}
          keyExtractor={identity}
        />
      </Tabs.Tab>
      <Tabs.Tab name="B">
        <Tabs.ScrollView>
          <View style={[styles.box, styles.boxA]} />
          <View style={[styles.box, styles.boxB]} />
        </Tabs.ScrollView>
      </Tabs.Tab>
    </Tabs.Container>
  )
}

const styles = StyleSheet.create({
  box: {
    height: 250,
    width: '100%',
  },
  boxA: {
    backgroundColor: 'white',
  },
  boxB: {
    backgroundColor: '#D8D8D8',
  },
  header: {
    height: HEADER_HEIGHT,
    width: '100%',
    backgroundColor: '#2196f3',
  },
})

export default Example

Guides

Scrolling on the Header

To enable scrolling from the header, follow these steps:

  • If the HeaderComponent does not contain touchable components, set the pointerEvents prop to 'none'.
  • If the HeaderComponent does contain touchable components, set the pointerEvents prop to 'box-none' to ensure they function properly.

Note: If any child component within the HeaderComponent should not respond to touches, such as an <Image /> element, set its pointerEvents prop to 'none'. Otherwise, it may unintentionally become the target of a touch gesture on iOS devices and prevent scrolling.

API Reference

Core

Tabs.Container

Basic usage looks like this:

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

const Example = () => {
   return (
     <Tabs.Container renderHeader={MyHeader}>
       <Tabs.Tab name="A">
         <ScreenA />
       </Tabs.Tab>
       <Tabs.Tab name="B">
         <ScreenB />
       </Tabs.Tab>
     </Tabs.Container>
   )
}

Props

name type default description
allowHeaderOverscroll boolean | undefined false Whether the header moves down during overscrolling (for example on pull-to-refresh on iOS) or sticks to the top
cancelLazyFadeIn boolean | undefined |
cancelTranslation boolean | undefined |
containerStyle StyleProp<ViewStyle> |
headerContainerStyle StyleProp<AnimateStyle<ViewStyle>> |
headerHeight number | undefined |Is optional, but will optimize the first render.
initialTabName string | undefined |
lazy boolean | undefined |If lazy, will mount the screens only when the tab is visited. There is a default fade in transition.
minHeaderHeight number | undefined |Header minimum height when collapsed
onIndexChange ((index: number) => void) | undefined |Callback fired when the index changes. It receives the current index.
onTabChange (data: { prevIndex: number index: number prevTabName: T tabName: T }) => void |Callback fired when the tab changes. It receives the previous and current index and tabnames.
pagerProps Omit<FlatListProps<number>, 'data' | 'keyExtractor' | 'renderItem' | 'horizontal' | 'pagingEnabled' | 'onScroll' | 'showsHorizontalScrollIndicator' | 'getItemLayout'> |Props passed to the pager. If you want for example to disable swiping, you can pass { scrollEnabled: false }
renderHeader (props: TabBarProps<TabName>) => React.ReactElement | null |
renderTabBar (props: TabBarProps<TabName>) => React.ReactElement | null (props: TabBarProps<TabName>) => MaterialTabBar
revealHeaderOnScroll boolean | undefined |Reveal header when scrolling down. Implements diffClamp.
snapThreshold number | null | undefined null Percentage of header height to define as the snap point. A number between 0 and 1, or null to disable snapping.
tabBarHeight number | undefined |Is optional, but will optimize the first render.
width number | undefined |Custom width of the container. Defaults to the window width.

Tabs.Tab

Wrap your screens with Tabs.Tab. Basic usage looks like this:

<Tabs.Container ...>
  <Tabs.Tab name="A" label="First Tab">
   <ScreenA />
  </Tabs.Tab>
  <Tabs.Tab name="B">
   <ScreenA />
  </Tabs.Tab>
</Tabs.Container>

Props

name type
label string | ((props: TabItemProps<T>) => ReactNode) | undefined
name T

Tabs.Lazy

Typically used internally, but if you want to mix lazy and regular screens you can wrap the lazy ones with this component.

Props

name type
cancelLazyFadeIn boolean | undefined
startMounted boolean | undefined

Tabs.FlatList

Use like a regular FlatList.

Tabs.FlashList

Use like a regular FlashList.

Tabs.MasonryFlashList

Use like a regular MasonryFlashList.

Tabs.ScrollView

Use like a regular ScrollView.

Tabs.SectionList

Use like a regular SectionList.

Ref

You can pass a ref to Tabs.Container.

const ref = React.useRef()
<Tabs.Container ref={ref}>
method type
jumpToTab (name: T) => boolean
setIndex (index: number) => boolean
getFocusedTab () => T
getCurrentIndex () => number

Hooks

useCollapsibleStyle

This hook provides access to key styles for the collapsible tab view. It can be used to obtain the progressViewOffset and pass it to the RefreshControl of the scroll view.

const {
  contentContainerStyle,
  progressViewOffset,
  style,
} = useCollapsibleStyle()

Values

name type
contentContainerStyle { minHeight: number; paddingTop: number; }
progressViewOffset number
style { width: number; }

useAnimatedTabIndex

This hook returns an animated value representing the current tab index. As the tab view can be in between panes while swiping, this value is a floating-point number.

const tabIndex = useAnimatedTabIndex()

useFocusedTab

This hook returns the name of the currently focused tab.

const focusedTab = useFocusedTab()

useHeaderMeasurements

This hook returns the top distance and the header height. For an example of how to use this, check out the animated header example in the example folder.

const { top, height } = useHeaderMeasurements()

useCurrentTabScrollY

This hook returns the vertical scroll position of the current tab as an Animated SharedValue.

Since this library requires handling the onScroll event for its functionality, this is the only way to react to changes in the scroll position of the underlying scrollable component.

const scrollY = useCurrentTabScrollY()

Default Tab Bar

MaterialTabItem

Any additional props are passed to the pressable component.

Props

name type description
activeColor string | undefined Color applied to the label when active
inactiveColor string | undefined Color applied to the label when inactive
inactiveOpacity number | undefined
index number
indexDecimal SharedValue<number>
label string | ((props: TabItemProps<T>) => ReactNode)
labelStyle StyleProp<AnimateStyle<TextStyle>> Style to apply to the tab item label
name T
onLayout (((event: LayoutChangeEvent) => void) & ((event: LayoutChangeEvent) => void)) | undefined Invoked on mount and layout changes with {nativeEvent: { layout: {x, y, width, height}}}.
onPress (name: T) => void
pressColor string | undefined
pressOpacity number | undefined
scrollEnabled boolean | undefined
style StyleProp<ViewStyle> Either view styles or a function that receives a boolean reflecting whether the component is currently pressed and returns view styles.

Known Issues

Android FlatList Pull to Refresh

Refer to this open issue. We utilize scrollTo to synchronize the unfocused tabs. While it is intended for use with ScrollView, it works well with FlatList, until the RefreshControl is added. Note that this issue occurs only on Android.

Workaround: Check out the Android Shared Pull To Refresh example in the expo app. You can implement a single pull-to-refresh for the Tabs.Container.

iOS FlatList StickyHeaderIndices and iOS SectionList StickySectionHeadersEnabled

When using the stickyHeaderIndices prop on a FlatList or stickySectionHeadersEnabled on a SectionList, the sticky elements do not scroll up as the header collapses. This issue is specific to iOS.

See #136.

ref.setIndex

This is not an issue per se, but it's essential to be aware of it. When using containerRef.current.setIndex(i), if you set it to the current index, the screen will scroll to the top. You can prevent this behavior as follows:

const index = pageRef.current?.getCurrentIndex()
if (index !== nextIndex) {
  pageRef.current?.setIndex(nextIndex)
}

Alternative Libraries

If you do not require a full-featured tab view, consider another option: a simple segmented control / material tab bar without swiping or snapping, using only the React Native Animated API.

Contributing and running the Example

While developing, you can run the example app to test your changes.

First run yarn in root:

yarn

Then prepare the example:

cd example
yarn

Then run the example:

yarn ios

Please follow the angular commit message format.

Make sure your code passes TypeScript and ESLint. Run the following to verify:

yarn typescript
yarn lint

To fix formatting errors, run the following:

yarn lint -- --fix

Documentation changes

Edit the README_TEMPLATE, or update the docstrings inside the src folder, and run:

yarn docs

changelog

8.0.0 (2024-07-05)

Code Refactoring

  • hooks, container: perf: avoid reading .value outside worklet (08a1c51)

BREAKING CHANGES

  • hooks, container: headerHeight, tabBarHeight, containerHeight, and contentInset are no longer SharedValues.

If you consume useHeaderMeasurements and/or useTabsContext expect this to impact you.

7.0.1 (2024-04-15)

Bug Fixes

  • dynamic tab sync (75c6e3a)
  • indicator display for single tab (ed67c03)

7.0.0 (2024-04-04)

chore

BREAKING CHANGES

  • Peer dependency changed to reanimated 3.8.1

6.2.2 (2024-04-03)

Bug Fixes

  • lazy FlashList scroll sync issue (1b89ec7)

6.2.1 (2023-08-10)

Bug Fixes

  • recycling and re-rendering issues (f1cbbc8)

6.2.0 (2023-08-01)

Features

6.1.4 (2023-04-27)

Bug Fixes

  • pass actual FlashList ref to consumer (bb019c4)

6.1.3 (2023-04-27)

Bug Fixes

6.1.2 (2023-04-27)

Bug Fixes

  • scroll restoration when using Lazy (7c0ec48)

6.1.1 (2023-04-22)

Bug Fixes

  • flashlist rerender issue (0fc3092)
  • flashlist with allowHeaderOverscroll (6d04616)

6.1.0 (2023-04-21)

Bug Fixes

  • collapsible-style: update min header height example (aff6c60)
  • collapsible-style: use minHeaderHeight when calculating the iOS min height (bd373ac)

Features

6.0.1 (2023-03-04)

  • updated readme

6.0.0 (2023-03-04)

Bug Fixes

  • lazy loading glitch with reanimated 3 (de032e9)

Features

  • reanimated v3 compatibility (f2e0f21)

BREAKING CHANGES

  • This version is only compatible with reanimated >= 3.0.1

6.0.0-rc.0 (2023-03-04)

Features

  • reanimated v3 compatibility (f2e0f21)

BREAKING CHANGES

  • This version is only compatible with reanimated >= 3.0.1

5.0.0 (2022-06-29)

Performance Improvements

  • snap using animated reaction scroll (5f11c61)

Bug Fixes

  • prevent typescript error for conditional tabs (749c27b)
  • initial scroll position sometimes wrong on iOS (e349941)
  • initial scroll position if starting with undefined header height (9378af6)
  • bring back pagerProps (2137bbd)
  • ios: scroll sync issues between tabs (14dfc79)

Features

  • custom label component (51a7234)
  • keepActiveTabCentered property on scrollable MaterialTabBar to keep tab in the center (6d35e31)
  • useCurrentTabScrollY (73ee5d7)

Code Refactoring

  • remove obsolete HeaderComponent and FooterComponent (cb2cb04)

BREAKING CHANGES

  • use renderHeader and renderTabBar instead of HeaderComponent and FooterComponent
  • a peer dependency on react-native-pager-view@5 is now required
  • useHeaderMeasurements now returns the height as an Animated.SharedValue

4.5.2 (2022-01-15)

Bug Fixes

  • edge case with tabs still scrolling to top (#230) (dd13cbc)

4.5.1 (2022-01-15)

Bug Fixes

  • minimum height with allowHeaderOverscroll (#229) (1e8ce9c)

4.5.0 (2022-01-15)

Bug Fixes

Features

  • allow header to move down on pull to refresh on iOS (#228) (d0e063b)

4.4.0 (2021-11-04)

Features

4.3.0 (2021-10-03)

Bug Fixes

  • lint and typescript errors (dc5fd39)
  • tab indicator did not work correctly in RTL mode (#193) (243848d)

Features

  • SectionList support (without stickyHeaders for now) (#201) (1571a62)

4.2.1 (2021-07-28)

Bug Fixes

Performance Improvements

4.2.0 (2021-05-28)

Features

Features

  • support custom refresh control component (273e0e7)

4.0.1 (2021-02-25)

Bug Fixes

  • rework contentHeights to use a shared value (#146) (0cecd27)

4.0.0 (2021-02-18)

Features

4.0.0-rc.11 (2021-02-18)

Bug Fixes

  • touchables not registering touches after scrolling (5598b11), closes #123

4.0.0-rc.10 (2021-02-17)

Bug Fixes

4.0.0-rc.9 (2021-02-16)

Performance Improvements

4.0.0-rc.8 (2021-02-15)

Bug Fixes

  • assume headerHeight is 0 if HeaderComponent isn't provided (#132) (418c917)

4.0.0-rc.7 (2021-02-15)

Bug Fixes

  • indicator glitch because scrollX was being reset (#131) (49a1348)

4.0.0-rc.6 (2021-02-14)

Bug Fixes

4.0.0-rc.5 (2021-02-13)

Bug Fixes

  • iOS issues when header height is undefined (#126) (5b2711b)

4.0.0-rc.4 (2021-02-13)

Bug Fixes

4.0.0-rc.3 (2021-02-11)

Bug Fixes

  • switching lazy tabs sometimes jumps scroll position on ios (#120) (6c3e9fd)

4.0.0-rc.2 (2021-02-10)

Bug Fixes

  • tab sync on ios when scrolling to the top (15ff5fc), closes #104

4.0.0-rc.1 (2021-02-10)

Bug Fixes

  • conditional dynamic tabs (51bf7cf), closes #102
  • content inset broken on react native (040e5c8), closes #113
  • ensure top line (1px) is visible on iOS (#112) (d832baf)
  • exclude documentation from library build (6826aa0), closes #105

4.0.0-rc.0 (2021-02-09)

Bug Fixes

  • add isGliding back + interpolate scrollYCurrent only in iOS (80a5690)
  • add null check (fb3b1b2)
  • attempt at fixing lazy sometimes opening with 0 opacity (264cf00)
  • bug when removing dynamic tab (d9c27a5)
  • cancel snapping animations on drag (63fa2dc)
  • contentHeight should've been a map (321fa34)
  • diffclamp snap threshold (356cdbf)
  • don't bounce pager on ios (fb49a97)
  • ensure callers can't override onScroll (8421c65)
  • handle ios overscroll (72b4e54)
  • infinite loop (f6a2f69)
  • initial lazy position on ios (56789c1)
  • ios refresh control (c09a3e8)
  • lazy (b383af9)
  • memoize tabbar in example so it doesn't flicker (eeb94cc)
  • minor tabbar tweaks (b0cdcff)
  • navigation bar zindex on ios (9fa6958)
  • opacity stuck as 0 on lazy tab (sometimes) (8cfc596)
  • prevent scrolling to top (1db8e4e)
  • rework refs to fix scroll sync (ec513cd)
  • sticky header edge case on android (7569a52)
  • stop animated scroll on snap (72e87ac)
  • sync scroll position on dynamic tabs (4c9cfce)
  • usederivedvalue for tabnames value (92f22de)

Features

  • add sanity check (duplicate tabs not allowed) (5e18678)
  • allow callers to still hook into certain events we're also handling (6f32f1c)
  • dynamic tabbar (f4ebdfe)
  • dynamic tabs (2884bba)
  • forward flatlist and scrollview refs (749cfbe)
  • rename onIndexChange to onTabChange and add onIndexChange(number) (c02c88a)

BREAKING CHANGES

  • createCollapsibleTabs() no longer exists. Instead the library exports different components (Container, Tab, ScrollView, ListView) which can be nested like normal react components.
  • refMap no longer exists, and creating refs is no longer necessary (it's all handled internally)
  • ScrollView/FlatList now forward their refs, so the caller can just tap into their ref prop
  • the existing onIndexChange was renamed to onTabChange, and a separate onIndexChange was implemented that just returns the new index (as a number)
  • diffClampEnabled was changed to revealHeaderOnScroll
  • redundant prop snapEnabled was removed, instead snapThreshold is number | null | undefined. If not a number, then snap is not enabled. Setting it to 0.5 will result in snapping in the middle, like in the previous version.

Fixes: #88, #94, #98, #100

3.8.0 (2021-02-09)

Features

  • add minHeaderHeight prop (0036da3), closes #95 #5
  • add tab item customization props in tab bar (fe4ac3c), closes #82

3.7.1 (2021-02-01)

Bug Fixes

  • ensure correct tab label ordering (1c2e65e), closes #77
  • handle scroll to top ios (d96f2e2), closes #66

3.7.0 (2021-01-30)

Bug Fixes

  • add missing tabbar style prop and improve typing (826966d), closes #64
  • change keyExtactor to use index and to be useCallback (95be6c2)
  • ensure children is not undefined (bd4f0e2), closes #68 #72

Features

  • add imperactive ref handler (a6c20fe), closes #71
  • add onIndexChange prop (15a516c), closes #71
  • export useCollapsibleStyle hook (dde28fe)

3.6.3 (2021-01-30)

Bug Fixes

  • allow flatlist to bounce (f359f80)
  • fix ios without momentum scrolling (87e3124)

Performance Improvements

  • optimize useAnimatedReaction (9b5b0a9)

3.6.2 (2021-01-30)

Bug Fixes

  • start with correct calculateNextOffset.value (fb405f7)

3.6.1 (2021-01-29)

Bug Fixes

  • prevent diffclamp not syncing on tab press (1ec9dcd)

3.6.0 (2021-01-29)

Features

  • add tab name context, remove name prop from scrollable components (c5166d2)

3.5.0 (2021-01-27)

Bug Fixes

  • correct typo "cancelLazyFazeIn" (2b8b194)
  • fix diffClamp tabs sync (304e8cc)
  • prevent not syncing on tab item press and momentum scrolling (5c5ff49)

Features

  • add initialTabName prop (7166592)

3.4.0 (2021-01-26)

Features

  • add fade-in if headerHeight is undefined (21d9be0)
  • add pressableProps to tab item (5ff176e)

3.3.1 (2021-01-26)

Bug Fixes

  • prevent not updating translateY if not providing the headerHeight (71cf7f4), closes #52

3.3.0 (2021-01-26)

Bug Fixes

  • scroll to correct position after window resizing (02da5a9)

Features

3.2.1 (2021-01-25)

Bug Fixes

  • replace Dimensions by useWindowDimensions (d21b4b5)

3.2.0 (2021-01-25)

Features

3.1.0 (2021-01-24)

Features

3.0.1 (2021-01-23)

Bug Fixes

  • prevent rerenders on layout (2e3aceb)

3.0.0 (2021-01-22)

Features

BREAKING CHANGES

  • remove react-native-tab-view

v3 has nothing backward compatible, it's a completely new API.

2.0.2 (2021-01-14)

Bug Fixes

2.0.1 (2021-01-14)

Bug Fixes

  • prevent not showing tab bar if no renderHeader (232561e), closes #39

2.0.0 (2021-01-05)

Bug Fixes

  • pass isGliding ref instead of current value and update types (37bf1b8), closes #33

BREAKING CHANGES

  • pass isGliding ref instead of isGliding.current

Migration: use isGliding.current instead of isGliding inside your custom renderTabBar function

1.5.0 (2020-12-29)

Features

1.4.2 (2020-12-15)

Bug Fixes

  • deps: remove peerDependenciesMeta field (a681d0a)

1.4.1 (2020-12-09)

Code Refactoring

  • export createContext helper (24a1185)

1.4.0 (2020-12-07)

Bug Fixes

  • prevent iOS infinite loop(e1d9db2)

Features

1.3.1 (2020-12-04)

Bug Fixes

  • prevent header not showing (534aa58), closes #16

1.3.0 (2020-12-04)

Features

  • add content container minHeight (a633910), closes #6 #8

1.2.0 (2020-12-02)

Features

  • allow overriding route key property (c7ea905)
  • add react-navigation integration (e34a34b), closes #7

1.1.3 (2020-12-02)

Bug Fixes

  • remove minHeight from contentContainerStyle (fa0e2ed), closes #8
  • snapping when momentum scrolling (887b0d9)

1.1.2 (2020-11-30)

Bug Fixes

  • prevent blank space on small content (98f7b7e), closes #8

1.1.1 (2020-11-20)

1.1.0 (2020-11-20)

Features

  • progressViewOffset in useCollapsibleScene (13c444d)

1.0.3 (2020-11-20)

Bug Fixes

1.0.2 (2020-11-19)

1.0.1 (2020-11-19)

1.0.0 (2020-11-19)

Features

  • add collapsible tab view (ac0696b)