Horizontal pagers in Jetpack Compose Glimmer

Applicable XR devices
This guidance helps you build experiences for these types of XR devices.
Display Glasses

In Jetpack Compose Glimmer, GlimmerHorizontalPager is a lazily composed, horizontally scrollable layout that arranges its pages sequentially. It's similar to the standard HorizontalPager found in Compose Foundation, but tailored for display glasses with Glimmer behaviors and default values.

By default, only one page is prominently displayed at a time. To provide a polished experience, the pager uses snap animations to ensure that a page always settles exactly into the viewport boundaries after a user's scrolling gesture ends.

Figure 1. An example of `GlimmerHorizontalPager`.

Key parameters and layout options

A GlimmerHorizontalPager provides customizable parameters to control spacing, layout alignment, and lazy loading. Some of these parameters are:

Parameter Description

state

A GlimmerPagerState object that manages, observes, and programmatically controls the pager's scroll position and active page.

contentPadding

Padding applied around the overall content boundaries after clipping, useful for adding leading or trailing edge padding before the first page or after the last page.

pageSpacing

The horizontal spacing between individual pages in the pager.

pageIndicator

A composable slot rendering the active page indicator. Defaults to GlimmerHorizontalPagerDefaults.PageIndicator(state).

beyondViewportPageCount

The number of pages to compose and lay out beyond the visible viewport as a pre-loading optimization. Avoid setting large values to preserve lazy composition efficiency.

See the full reference documentation for information on all available parameters.

Animated text motion recommendation

During pager snap animations and transitions on display glasses, default text rendering can exhibit pixel-snapping artifacts. Setting TextMotion.Animated ensures smooth rendering throughout layout animations:

Text(
    text = "Page: $page",
    style = LocalTextStyle.current.copy(textMotion = TextMotion.Animated),
)

Example: Horizontal pager

The following code demonstrates how to create a basic horizontal pager with 10 pages, placing a Card inside each page:

// Hoist the pager state, specifying the total page count with a lambda.
val pagerState = rememberGlimmerPagerState(pageCount = { 10 })

GlimmerHorizontalPager(
    state = pagerState,
    modifier = Modifier.fillMaxSize(),
) { page ->
    // Use Glimmer components like Card and Text for optimized glasses styling.
    Card(modifier = Modifier.fillMaxWidth()) {
        Text(
            text = "Page: $page",
            // Recommended: use TextMotion.Animated for smooth transitions in a pager.
            style = LocalTextStyle.current.copy(textMotion = TextMotion.Animated),
        )
    }
}

Key points about the code

  • State: Initializes a GlimmerPagerState using rememberGlimmerPagerState(pageCount = { 10 }) to manage the state of the pager.
  • Page slot content: Receives the page index page inside the GlimmerPagerScope lambda to render each card.
  • Smooth animation text style: Copies LocalTextStyle.current and explicitly enables TextMotion.Animated.
  • Automatic page indicator: Unlike standard Compose pagers that require an external indicator component, GlimmerHorizontalPager automatically embeds a dot-style page indicator by default.

Page indicators

By default, GlimmerHorizontalPager renders a dot-based page indicator using GlimmerHorizontalPagerDefaults.PageIndicator. The indicator automatically adapts its color scheme:

  • The active dot uses the content color resolved from the nearest surrounding surface.

You can pass a customized PageIndicator to specify explicit colors, or replace the dot with your own custom layout:

GlimmerHorizontalPager(
    state = pagerState,
    modifier = Modifier.fillMaxSize(),
    // Use a page numbers instead of the default dot-indicator
    pageIndicator = {
        Text(
            text = "${pagerState.currentPage + 1} / ${pagerState.pageCount}",
            style = LocalTextStyle.current.copy(textMotion = TextMotion.Animated),
        )
    }
)