Paging lets users swipe horizontally or vertically between distinct, full-screen pages on Wear OS devices. Common use cases include swiping between workout metrics and media controls in an exercise app, or stepping through multi-page flows.
In Compose for Wear OS Material 3, HorizontalPagerScaffold and
VerticalPagerScaffold coordinate the pager layout, automatically
positioning the page indicator and managing transitions with TimeText.
Each page is wrapped in an AnimatedPage composable, which applies
scaling, rounded corner morphing, and scrim effects as pages transition across
the round display.
The following animation shows how HorizontalPagerScaffold and AnimatedPage
scale and animate pages during horizontal swipes:
Scaffold hierarchy for paging
When designing paginated screen layouts in Material 3—whether swiping
left or right (HorizontalPager) or up or down (VerticalPager)—use the
following component hierarchy from outer container to inner content:
AppScaffold: The outermost container at the root of your app (use only one per app). It anchors the globalTimeTextoverlay so that the clock remains stationary at the top of the screen during page transitions and swipe-to-dismiss gestures.HorizontalPagerScaffoldorVerticalPagerScaffold: Placed insideAppScaffoldat the pager level. It coordinates transitions betweenTimeTextand theHorizontalPageIndicatororVerticalPageIndicator.HorizontalPagerorVerticalPager: The foundation pager container that manages swipe gestures, fling physics, and rotary input snapping using a sharedPagerState.AnimatedPage: Placed inside the pager's page content lambda. It wraps each individual page to apply Material 3 transition animations (scaling and scrim effects) based on the page's offset inPagerState.ScreenScaffold: Placed insideAnimatedPagefor each individual page. Because each page can contain its own vertically scrollable list (such as aTransformingLazyColumn) or its ownEdgeButton, nestingScreenScaffoldinside each page ensures that vertical scroll indicators, edge buttons, andcontentPaddingbelong to that specific page and animate smoothly with it.
AppScaffold (1 per app: anchors global TimeText)
└── HorizontalPagerScaffold / VerticalPagerScaffold (manages PageIndicator)
└── HorizontalPager / VerticalPager (manages PagerState & fling behavior)
└── AnimatedPage (applies scaling & scrim transitions per page)
└── ScreenScaffold (1 per page: ScrollIndicator & EdgeButton)
└── Page Content (Column or TransformingLazyColumn)
Implement a horizontal pager
To implement a horizontal pager in Material 3, nest HorizontalPagerScaffold,
HorizontalPager, AnimatedPage, and ScreenScaffold inside your app's
AppScaffold.
Use PagerScaffoldDefaults.snapWithSpringFlingBehavior for
flingBehavior to apply Material 3 spring motion physics and responsive page
snapping (HighSnapPositionalThreshold). By default, HorizontalPager disables
rotary page scrolling (rotaryScrollableBehavior = null), allowing rotating
crown or bezel input to scroll vertical lists (such as a
TransformingLazyColumn) inside the active page.
The following sample demonstrates a complete HorizontalPagerScaffold setup:
@Composable fun HorizontalPagerScaffoldSample(navigateBack: () -> Unit) { AppScaffold { val pagerState = rememberPagerState(pageCount = { 10 }) HorizontalPagerScaffold(pagerState = pagerState) { HorizontalPager( state = pagerState, flingBehavior = PagerScaffoldDefaults.snapWithSpringFlingBehavior( state = pagerState ), ) { page -> AnimatedPage(pageIndex = page, pagerState = pagerState) { ScreenScaffold { Column( modifier = Modifier.fillMaxSize(), horizontalAlignment = Alignment.CenterHorizontally, verticalArrangement = Arrangement.Center, ) { Text(text = "Page #$page") Spacer(modifier = Modifier.height(8.dp)) Text(text = "Swipe left and right") if (page == 0) { Spacer(modifier = Modifier.height(16.dp)) Button(onClick = navigateBack) { Text("Exit") } } } } } } } } }
If a page inside your horizontal pager contains a TransformingLazyColumn,
create a separate rememberTransformingLazyColumnState() inside that page's
scope and pass it to the inner ScreenScaffold and TransformingLazyColumn:
AppScaffold { val pagerState = rememberPagerState(pageCount = { 10 }) HorizontalPagerScaffold(pagerState = pagerState) { HorizontalPager( state = pagerState, flingBehavior = PagerScaffoldDefaults.snapWithSpringFlingBehavior( state = pagerState ), ) { page -> AnimatedPage(pageIndex = page, pagerState = pagerState) { val columnState = rememberTransformingLazyColumnState() val transformationSpec = rememberTransformationSpec() ScreenScaffold( scrollState = columnState, ) { contentPadding -> TransformingLazyColumn( state = columnState, contentPadding = contentPadding, ) { item { ListHeader( modifier = Modifier .fillMaxWidth() .transformedHeight(this, transformationSpec) .minimumVerticalContentPadding( ListHeaderDefaults.minimumTopListContentPadding ), transformation = SurfaceTransformation(transformationSpec), ) { Text(text = "Pager sample") } } item { Card( modifier = Modifier .fillMaxWidth() .transformedHeight(this, transformationSpec) .minimumVerticalContentPadding( CardDefaults.minimumVerticalListContentPadding ), transformation = SurfaceTransformation(transformationSpec), ) { if (page == 0) { Text(text = "Page #$page. Swipe right") } else { Text(text = "Page #$page. Swipe left and right") } } } } } } } } }
Implement a vertical pager
A vertical pager lets users swipe up and down between pages or step through them using the watch's rotating side button or bezel.
Unlike HorizontalPager, VerticalPager enables rotary page snapping by
default (RotaryScrollableDefaults.snapBehavior(state)). Pass
PagerScaffoldDefaults.snapWithSpringFlingBehavior to flingBehavior and wrap
each page in AnimatedPage and ScreenScaffold:
@Composable fun VerticalPagerScaffoldSample() { AppScaffold { val pagerState = rememberPagerState(pageCount = { 10 }) VerticalPagerScaffold(pagerState = pagerState) { VerticalPager( state = pagerState, flingBehavior = PagerScaffoldDefaults.snapWithSpringFlingBehavior( state = pagerState ), ) { page -> AnimatedPage(pageIndex = page, pagerState = pagerState) { ScreenScaffold { Column( modifier = Modifier.fillMaxSize(), horizontalAlignment = Alignment.CenterHorizontally, verticalArrangement = Arrangement.Center, ) { Text(text = "Page #$page") Spacer(modifier = Modifier.height(8.dp)) Text(text = "Swipe up and down") } } } } } } }
Customize pager behavior
You can customize the snapping sensitivity of HorizontalPagerScaffold and
VerticalPagerScaffold to match your app's interaction needs.
Adjust snap sensitivity for workouts
During workouts or high-motion activities where the user's gross motor control is limited, accidental touches or slight crown rotations can unintentionally switch pages. For these screens, configure your pager with low snap sensitivity:
- Pass
PagerDefaults.snapFlingBehaviorwithmaxFlingPages = 0andsnapPositionalThreshold = PagerScaffoldDefaults.LowSnapPositionalThresholdso a deliberate drag across a larger portion of the screen is required to turn the page. - If you enable rotary page snapping, set
snapSensitivity = RotaryScrollableDefaults.LowSnapSensitivityinRotaryScrollableDefaults.snapBehaviorto require more rotation before snapping to the next page.
@Composable fun HorizontalPagerScaffoldWithLowSensitivitySample(navigateBack: () -> Unit) { AppScaffold { val pagerState = rememberPagerState(pageCount = { 3 }) HorizontalPagerScaffold(pagerState = pagerState) { HorizontalPager( state = pagerState, flingBehavior = PagerDefaults.snapFlingBehavior( state = pagerState, maxFlingPages = 0, snapPositionalThreshold = PagerScaffoldDefaults.LowSnapPositionalThreshold, ), rotaryScrollableBehavior = RotaryScrollableDefaults.snapBehavior( pagerState = pagerState, snapSensitivity = RotaryScrollableDefaults.LowSnapSensitivity, ), ) { page -> AnimatedPage(pageIndex = page, pagerState = pagerState) { ScreenScaffold { // Page content } } } } } }
For full sample implementations, see Pager.kt in the android/snippets
repository and PagerScaffoldSample.kt in the AndroidX repository.