TypeScript icon, indicating that this package has built-in type declarations

    4.5.2 • Public • Published


    Build Status Version MIT License runs with expo

    Expo app

    If you are looking for the integration with react-native-tab-view and/or react-navigation, you need to use the v2.

    Collapsible Tab View for React Native, with Reanimated.


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


    Default Snap revealHeaderOnScroll revealHeaderOnScroll + Snap


    • Animations and interactions on the UI thread
    • Highly customizable
    • Fully typed with TypeScript
    • Lazy support 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
    • Support horizontal and vertical window


    Open a Terminal in the project root and run:

    yarn add react-native-collapsible-tab-view@next

    Then, add Reanimated v2, 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={[, index % 2 === 0 ? styles.boxB : styles.boxA]} />
      }, [])
      return (
          headerHeight={HEADER_HEIGHT} // optional
          <Tabs.Tab name="A">
          <Tabs.Tab name="B">
              <View style={[, styles.boxA]} />
              <View style={[, styles.boxB]} />
    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


    Scroll on header

    If you want to allow scrolling from the header:

    • If the HeaderComponent doesn't contain touchables set pointerEvents='none'

    • If HeaderComponent does contain touchables set pointerEvents='box-none' for them to work.

      Note: With this setting any child component that should not respond to touches (e.g. <Image />) needs to have pointerEvents set to 'none'. Otherwise it can become the target of a touch gesture on iOS devices and thereby preventing scrolling.

    API reference



    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 name="B">
             <ScreenB />


    name type default description
    HeaderComponent ((props: TabBarProps<T>) => React.ReactElement) | null | undefined @obsolete use renderHeader instead. This property will be removed in 5.0.0
    TabBarComponent ((props: TabBarProps<T>) => React.ReactElement) | null | undefined MaterialTabBar @obsolete use renderTabBar instead. This property will be removed in 5.0.0
    allowHeaderOverscroll boolean 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 | number | 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 horiztontal flatlist. 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.


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

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


    name type
    label string | undefined
    name T


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


    name type
    cancelLazyFadeIn boolean | undefined
    startMounted boolean | undefined


    Use like a regular FlatList.


    Use like a regular ScrollView.


    Use like a regular SectionList.


    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



    Hook to access some key styles that make the whole think work. You can use this to get the progessViewOffset and pass to the refresh control of scroll view.

    const {
    } = useCollapsibleStyle()


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


    Returns an animated value representing the current tab index, as a floating point number.

    const tabIndex = useAnimatedTabIndex()


    Returns the currently focused tab name.

    const focusedTab = useFocusedTab()


    Returns the top distance and the header height. See the animated header example in the example folder.

    const { top, height } = useHeaderMeasurements()

    Default Tab Bar


    Any additional props are passed to the pressable component.


    name type default description
    activeColor string | undefined null Color applied to the label when active
    inactiveColor string | undefined null Color applied to the label when inactive
    inactiveOpacity number | undefined 0.7
    index number
    indexDecimal SharedValue<number>
    label string
    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 #DDDDDD
    pressOpacity number | undefined Platform.OS === 'ios' ? 0.2 : 1
    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

    See this open issue. We use scrollTo to synchronize the unfocused tabs, it's supposed to work only with ScrollView, but works great with FlatList, until the RefreshControl is added. Note that this happens only to android.

    Workaround: see the Android Shared Pull To Refresh example in the expo app. You can have a single pull to refresh for the Tabs.Container.

    iOS FlatList stickyHeaderIndices and iOS SectionList stickySectionHeadersEnabled

    When you use the stickyHeaderIndices prop on a FlatList or stickySectionHeadersEnabled on a SectionList, the sticky elements don't scroll up when the header collapses. This happens only on iOS.

    See #136.


    This isn't an issue, but you need to know. When using containerRef.current.setIndex(i), if setting to the current index, the screen will scroll to the top. You can prevent this behavior like this:

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

    Alternative libraries

    If you don't need a full-featured tab view, check out the other option, a simple segmented control / material tab bar without swiping or snapping, using just the react native Animated API.


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

    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

    Remember to add tests for your change if possible.

    Documentation changes

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

    yarn docs


    npm i react-native-collapsible-tab-view

    DownloadsWeekly Downloads






    Unpacked Size

    479 kB

    Total Files


    Last publish


    • andrei.alecu
    • pedrobern