[ Web Proxy ]
URL:
Viewing: https://developer.android.com/develop/ui/compose/touch-input/scroll/scroll-modifiers [Back]  [Original]

Scroll modifiers  |  Jetpack Compose  |  Android Developers Skip to main content
Essentials Design & Plan Develop Google Play Blog
Search:
Android Studio
Android Developers [Android Developers]

Scroll modifiers Stay organized with collections Save and categorize content based on your preferences.

Note: If you want to show a list of items, consider using LazyColumn and LazyRow instead of these APIs. LazyColumn and LazyRow feature scrolling, and they are much more efficient than the scrolling modifier because they only compose the items as they're needed. See the Lists and grids documentation for more information.

The verticalScroll and horizontalScroll modifiers provide the simplest way to allow the user to scroll an element when the bounds of its contents are larger than its maximum size constraints. With the verticalScroll and horizontalScroll modifiers you don't need to translate or offset the contents.

@Composable
private fun ScrollBoxes() {
    Column(
        modifier = Modifier
            .background(Color.LightGray)
            .size(100.dp)
            .verticalScroll(rememberScrollState())
    ) {
        repeat(10) {
            Text("Item $it", modifier = Modifier.padding(2.dp))
        }
    }
}

A simple vertical list responding to scroll
gestures [A simple vertical list responding to scroll gestures] Figure 1. A simple vertical list responding to scroll gestures.

The ScrollState lets you change the scroll position or get its current state. To create it with default parameters, use rememberScrollState().

@Composable
private fun ScrollBoxesSmooth() {
    // Smoothly scroll 100px on first composition
    val state = rememberScrollState()
    LaunchedEffect(Unit) { state.animateScrollTo(100) }

    Column(
        modifier = Modifier
            .background(Color.LightGray)
            .size(100.dp)
            .padding(horizontal = 8.dp)
            .verticalScroll(state)
    ) {
        repeat(10) {
            Text("Item $it", modifier = Modifier.padding(2.dp))
        }
    }
}

Scrollable area modifier

The scrollableArea modifier is a fundamental building block for creating custom scrollable containers. It provides a higher-level abstraction over the scrollable modifier, handling common requirements like gesture delta interpretation, content clipping, and overscroll effects.

While scrollableArea is used for custom implementations, you should generally prefer ready-made solutions like verticalScroll, horizontalScroll, or composables like LazyColumn for standard scrolling lists. These higher-level components are simpler for common use cases and are themselves built by using scrollableArea.

Difference between scrollableArea and scrollable modifiers

The main difference between scrollableArea and scrollable lies in how they interpret user scroll gestures:

Think of it like this: scrollable tells you how the pointer moved, while scrollableArea translates that pointer movement into how the content should move within a typical scrollable view. This inversion is why scrollableArea feels more natural when implementing a standard scrollable container.

The following table summarizes the delta signs for common scenarios:

User gesture

delta reported to dispatchRawDelta by scrollable

delta reported to dispatchRawDelta by scrollableArea*

Pointer moves UP

Negative

Positive

Pointer moves DOWN

Positive

Negative

Pointer moves LEFT

Negative

Positive (Negative for RTL)

Pointer moves RIGHT

Positive

Negative (Positive for RTL)

(*) Note on scrollableArea delta sign: The sign of the delta from scrollableArea is not just a simple inversion. It intelligently considers:

  1. Orientation: Vertical or horizontal.
  2. LayoutDirection: LTR or RTL (especially important for horizontal scrolling).
  3. reverseScrolling flag: Whether scroll direction is inverted.

In addition to inverting the scroll delta, scrollableArea also clips the content to the bounds of the layout and handles the rendering of overscroll effects. By default, it uses the effect provided by LocalOverscrollFactory. You can customize or disable this by using the scrollableArea overload that accepts an OverscrollEffect parameter.

When to use scrollableArea modifier

You should use the scrollableArea modifier when you need to build a custom scrolling component that isn't adequately served by the horizontalScroll or verticalScroll modifiers or Lazy layouts. This often involves cases with:

Create custom wheel-like lists using scrollableArea

The following sample demonstrates using scrollableArea to build a custom vertical list where items scale down as they move away from the center, creating a "wheel-like" visual effect. This kind of scroll-dependent transformation is a perfect use case for scrollableArea.

Figure 2. A customized vertical list using scrollableArea.

@Composable
private fun ScrollableAreaSample() {
    // ...
    Layout(
        modifier =
            Modifier
                .size(150.dp)
                .scrollableArea(scrollState, Orientation.Vertical)
                .background(Color.LightGray),
        // ...
    ) { measurables, constraints ->
        // ...
        // Update the maximum scroll value to not scroll beyond limits and stop when scroll
        // reaches the end.
        scrollState.maxValue = (totalHeight - viewportHeight).coerceAtLeast(0)

        // Position the children within the layout.
        layout(constraints.maxWidth, viewportHeight) {
            // The current vertical scroll position, in pixels.
            val scrollY = scrollState.value
            val viewportCenterY = scrollY + viewportHeight / 2

            var placeableLayoutPositionY = 0
            placeables.forEach { placeable ->
                // This sample applies a scaling effect to items based on their distance
                // from the center, creating a wheel-like effect.
                // ...
                // Place the item horizontally centered with a layer transformation for
                // scaling to achieve wheel-like effect.
                placeable.placeRelativeWithLayer(
                    x = constraints.maxWidth / 2 - placeable.width / 2,
                    // Offset y by the scroll position to make placeable visible in the viewport.
                    y = placeableLayoutPositionY - scrollY,
                ) {
                    scaleX = scaleFactor
                    scaleY = scaleFactor
                }
                // Move to the next item's vertical position.
                placeableLayoutPositionY += placeable.height
            }
        }
    }
}
// ...

Scrollable modifier

Note: scrollable is a low level modifier that handles low level scrolling input gestures, without other behaviors commonly used for scrollable containers. For building scrollable containers, see androidx.compose.foundation.scrollableArea. scrollableArea clips its content to its bounds, renders overscroll, and adjusts the direction of scroll gestures to verify that the content moves with the user's gestures.

The scrollable modifier differs from the scroll modifiers in that scrollable detects the scroll gestures and captures the deltas, but does not offset its contents automatically. This is instead delegated to the user through ScrollableState , which is required for this modifier to work correctly.

When constructing ScrollableState you must provide a consumeScrollDelta function which will be invoked on each scroll step (by gesture input, smooth scrolling or flinging) with the delta in pixels. This function must return the amount of scrolling distance consumed, to ensure the event is properly propagated in cases where there are nested elements that have the scrollable modifier.

Note: The scrollable modifier does not affect the layout of the element it is applied to. This means that any changes to the element layout or its children must be handled through the delta provided by ScrollableState. It is also important to note that scrollable is not opinionated about children's layouts, which means it doesn't need to measure the children in order to propagate the scrolling delta.

The following snippet detects the gestures and displays a numerical value for an offset, but does not offset any elements:

@Composable
private fun ScrollableSample() {
    // actual composable state
    var offset by remember { mutableFloatStateOf(0f) }
    Box(
        Modifier
            .size(150.dp)
            .scrollable(
                orientation = Orientation.Vertical,
                // Scrollable state: describes how to consume
                // scrolling delta and update offset
                state = rememberScrollableState { delta ->
                    offset += delta
                    delta
                }
            )
            .background(Color.LightGray),
        contentAlignment = Alignment.Center
    ) {
        Text(offset.toString())
    }
}

A UI element detecting the finger press and displaying the numeric value for the finger's location [A UI element detecting the finger press and displaying the numeric value for the finger's location] Figure 3. A UI element detecting the finger press and displaying the numeric value for the finger's location.
Understand scroll phases
Nested scroll modifiers

Content and code samples on this page are subject to the licenses described in the Content License. Java and OpenJDK are trademarks or registered trademarks of Oracle and/or its affiliates.

Last updated 2026-08-14 UTC.

[[["Easy to understand","easyToUnderstand","thumb-up"],["Solved my problem","solvedMyProblem","thumb-up"],["Other","otherUp","thumb-up"]],[["Missing the information I need","missingTheInformationINeed","thumb-down"],["Too complicated / too many steps","tooComplicatedTooManySteps","thumb-down"],["Out of date","outOfDate","thumb-down"],["Samples / code issue","samplesCodeIssue","thumb-down"],["Other","otherDown","thumb-down"]],["Last updated 2026-08-14 UTC."],[],[]]

Web Proxy Viewer  |  New URL  |  Original Page