PagerView lets users swipe left and right (or up and down) through pages of content. It is a native component: on Android it renders a Jetpack Compose pager, and on iOS a SwiftUI paged TabView. See it in action!
npm install react-native-pager-view
# or
yarn add react-native-pager-view
# or
bun add react-native-pager-viewThe library is autolinked. On iOS, install the pods afterwards:
cd ios && pod installWe support the three latest React Native versions: 0.86, 0.87 and 0.88. Only the New Architecture is supported.
import { StyleSheet, View, Text } from 'react-native';
import PagerView from 'react-native-pager-view';
export function MyPager() {
return (
<PagerView style={styles.pagerView} initialPage={0}>
<View key="1">
<Text>First page</Text>
</View>
<View key="2">
<Text>Second page</Text>
</View>
</PagerView>
);
}
const styles = StyleSheet.create({
pagerView: {
flex: 1,
},
});Each direct child of PagerView becomes one page and is stretched to fill the pager. Give every child a unique key.
To control the pager from code, keep a ref and call its methods:
import PagerView, { type PagerViewRef } from 'react-native-pager-view';
const ref = useRef<PagerViewRef>(null);
// ...
<PagerView ref={ref} style={{ flex: 1 }}>
{pages}
</PagerView>;
// later
ref.current?.setPage(2);For more examples, see the example app: nested pagers, keyboard handling, tab views, pagination dots, carousels and more.
| Prop | Description | Platform |
|---|---|---|
initialPage: number |
Index of the page that is selected on mount. | both |
scrollEnabled: boolean |
Whether the user can swipe between pages. Defaults to true. |
both |
orientation: 'horizontal' | 'vertical' |
Scroll direction. Defaults to horizontal. Changing it after mount is not supported. |
both |
layoutDirection: 'ltr' | 'rtl' | 'locale' |
Layout direction. Use ltr or rtl to set it explicitly, or locale to follow I18nManager.isRTL. Defaults to locale. |
both |
pageMargin: number |
Blank space between pages. | both |
keyboardDismissMode: 'none' | 'on-drag' |
Whether the keyboard is dismissed when the user drags the pager. Defaults to none. |
both |
lazy: boolean |
Mounts a page's content only when the pager gets near it. See Lazy pages. Defaults to false. |
both |
lazyPreloadDistance: number |
With lazy, how many pages on each side of the current page are mounted ahead of time. Defaults to 1. |
both |
renderLazyPlaceholder: (index: number) => ReactNode |
With lazy, what a page shows before its content is mounted. |
both |
onPageScroll: (e: PagerViewOnPageScrollEvent) => void |
Called while moving between pages, either from a swipe or from an animated setPage. e.nativeEvent contains position and offset. |
both |
onPageSelected: (e: PagerViewOnPageSelectedEvent) => void |
Called when a new page becomes selected, and once on mount for initialPage. e.nativeEvent.position is the selected index. |
both |
onPageScrollStateChanged: (e: PageScrollStateChangedNativeEvent) => void |
Called when the scroll state changes. e.nativeEvent.pageScrollState is idle, dragging or settling. |
both |
overdrag: boolean |
Allows bouncing past the first and last page. Defaults to false. |
iOS |
offscreenPageLimit: number |
Number of pages kept composed on each side of the visible page. Defaults to 1. |
Android |
overScrollMode: 'auto' | 'always' | 'never' |
Overscroll effect at the edges. never disables the effect. Defaults to auto. |
Android |
PagerView also accepts all standard View props.
| Method | Description | Platform |
|---|---|---|
setPage(index: number, options?) |
Scrolls to the given page with an animation. An invalid index is ignored. { animation: 'direct' } slides the target in next to the current page instead of scrolling past the pages in between. |
both |
setPageWithoutAnimation(index: number) |
Jumps to the given page without an animation. An invalid index is ignored. | both |
setScrollEnabled(scrollEnabled: boolean) |
Enables or disables swiping imperatively. Prefer the scrollEnabled prop; use this when a re-render would get in the way, for example during an animation. |
both |
The ref is the native pager view with these methods attached, so native view methods such as measure() work on it too.
On iOS 16 and later the pager does not scroll itself to reveal a focused text input. UIKit reveals a first responder by scrolling the nearest scrolling ancestor, which for a pager is the view that pages, so an input focused on a page that is not shown would leave the pager between pages rather than change the page. Change the page with setPage and focus the input once the pager has come to rest there: onPageScrollStateChanged carries no page, so keep the position from the last onPageScroll that reported an offset of 0, which the pager sends just before it reports idle, and focus only when that position is the page you asked for. On iOS that position counts the pages from the left, while setPage and onPageSelected count them in reading order, so a right-to-left layout has to mirror one of them before comparing. On iOS onPageSelected arrives as the slide starts, where the page can still be out of the window and focus does nothing, and an idle can arrive while the pager is still on the page it started from. A field inside a page is still revealed by the scroll views within that page.
The package exports these TypeScript types: PagerViewRef (the ref type), PagerViewMethods, PagerViewProps, PagerViewOnPageScrollEvent, PagerViewOnPageSelectedEvent, PageScrollStateChangedNativeEvent, and their *EventData payloads.
By default, every page renders on mount. With lazy, a page renders only once the pager is within lazyPreloadDistance pages of it. After that it stays mounted, so it keeps its scroll position and state.
Pages are tracked by key, so adding or removing pages doesn't remount the others.
<PagerView
style={{ flex: 1 }}
lazy
renderLazyPlaceholder={() => (
<View style={{ flex: 1, justifyContent: 'center' }}>
<ActivityIndicator />
</View>
)}
>
{items.map((item) => (
<View key={item.id}>
<Item item={item} />
</View>
))}
</PagerView>renderLazyPlaceholder shows until a page mounts. With the default distance you'll rarely see it: only when an animated setPage passes pages that aren't mounted yet, at the start of a swipe with lazyPreloadDistance={0}, or when the JS thread is busy. It isn't a loading state for the page's data.
Set lazy when the pager mounts and leave it. The pager doesn't track the current page while lazy is off, so turning it on later mounts the pages around initialPage and shows placeholders elsewhere, including the page on screen, until the next swipe or setPage.
With lazy, onPageScroll events always reach JS, since that's how the pager tracks which pages to mount. This includes an Animated.event with useNativeDriver.
offscreenPageLimit is unrelated. It only limits how many pages the Android pager composes, and React still renders every page.
See the full example.
usePagerView bundles the state and handlers you usually wire up yourself: the ref, the active page, the scroll state, the scroll progress, and helpers to add or remove pages and to toggle scrolling, animation and overdrag. It also returns AnimatedPagerView, which is PagerView wrapped with Animated.createAnimatedComponent.
import { View, Text } from 'react-native';
import { usePagerView } from 'react-native-pager-view';
export function PagerHookExample() {
const { AnimatedPagerView, ref, ...rest } = usePagerView({ pagesAmount: 10 });
return (
<AnimatedPagerView
ref={ref}
style={{ flex: 1 }}
initialPage={0}
scrollEnabled={rest.scrollEnabled}
overdrag={rest.overdrag}
onPageScroll={rest.onPageScroll}
onPageSelected={rest.onPageSelected}
onPageScrollStateChanged={rest.onPageScrollStateChanged}
>
{rest.pages.map((page) => (
<View key={page}>
<Text>{`Page ${page}`}</Text>
</View>
))}
</AnimatedPagerView>
);
}The returned object also includes activePage, progress, scrollState, setPage, addPage, removePage, toggleScroll, toggleAnimation and toggleOverdrag. See the full example.
To handle onPageScroll on the UI thread with Reanimated, build an event handler with useEvent and pass it to an animated PagerView:
import PagerView from 'react-native-pager-view';
import Animated, { useEvent, useHandler } from 'react-native-reanimated';
const AnimatedPagerView = Animated.createAnimatedComponent(PagerView);
function usePageScrollHandler(handlers, dependencies) {
const { context, doDependenciesDiffer } = useHandler(handlers, dependencies);
return useEvent(
(event) => {
'worklet';
const { onPageScroll } = handlers;
if (onPageScroll && event.eventName.endsWith('onPageScroll')) {
onPageScroll(event, context);
}
},
['onPageScroll'],
doDependenciesDiffer
);
}
function MyPager() {
const handler = usePageScrollHandler({
onPageScroll: (e) => {
'worklet';
console.log(e.offset, e.position);
},
});
return <AnimatedPagerView style={{ flex: 1 }} onPageScroll={handler} />;
}See the full example.
| horizontal | vertical |
|---|---|
![]() |
![]() |
| horizontal | vertical |
|---|---|
![]() |
![]() |
PagerView is now a function component. Props did not change.
- Type refs with
PagerViewRefinstead ofPagerView. The ref is the native view with the methods attached;ref.current.pagerViewis deprecated. - Subclassing
PagerViewis no longer possible. Wrap it in your own component instead. - On Android,
keyboardDismissMode="on-drag"dismisses the keyboard only when a drag starts, not onsetPage. - On iOS, leaving a page now hides the keyboard if an input on that page is focused.
- 8.x → 9.x: the Android implementation moved from
ViewPager2to Jetpack Compose. The JavaScript API did not change. - 7.x → 8.x: the iOS implementation moved from
UIPageViewControllerto SwiftUI. The JavaScript API did not change. - 6.x → 7.x: only the New Architecture is supported. Enable it in your app before upgrading.
See the contributing guide to learn how to contribute to the repository and the development workflow.
MIT




