scrollableArea

Configure a component to act as a scrollable area. A scrollable area clips its content to its

scrollableArea

Compose Modifier

Common
fun Modifier.scrollableArea(
    state: ScrollableState,
    orientation: Orientation,
    enabled: Boolean = true,
    reverseScrolling: Boolean = false,
    flingBehavior: FlingBehavior? = null,
    interactionSource: MutableInteractionSource? = null,
    bringIntoViewSpec: BringIntoViewSpec? = null,
): Modifier

Configure a component to act as a scrollable area. A scrollable area clips its content to its bounds, renders overscroll, and handles scroll gestures such that the content, not the viewport, moves with the user's gestures.

This modifier is a building block for creating custom scrollable containers, and serves as a higher-level abstraction over androidx.compose.foundation.gestures.scrollable. For simpler use cases, prefer higher-level components that are built with scrollableArea, such as verticalScroll and androidx.compose.foundation.lazy.LazyColumn. For example, verticalScroll offsets the content in the viewport out of the box to have scrollable container behavior.

The primary distinction between scrollable and scrollableArea is in how scroll deltas are handled. scrollableArea inverts the deltas to provide a natural "content-moving" experience. For instance, dragging a finger up results in a positive scroll delta, which accommodates content moving upwards within the layout. In contrast, the lower-level scrollable provides raw, un-inverted deltas, which is useful for custom gesture handling that isn't directly tied to content scrolling.

The direction of scrolling is automatically adjusted based on the orientation, the current androidx.compose.ui.platform.LocalLayoutDirection, and the reverseScrolling flag. Setting reverseScrolling to true is useful for layouts that grow from the end of the container to the beginning, like a chat feed. In such cases, the content within the container should also be laid out in reverse. The following table summarizes the resulting scroll delta for a user's drag gesture:

orientationLayoutDirectionreverseScrollingUser GestureScroll Delta
VerticalLtr and RtlfalseDrag UpPositive
VerticalLtr and RtltrueDrag UpNegative
HorizontalLtrfalseDrag LeftPositive
HorizontalLtrtrueDrag LeftNegative
HorizontalRtlfalseDrag LeftNegative
HorizontalRtltrueDrag LeftPositive

This scrollableArea overload uses overscroll provided through LocalOverscrollFactory by default. See the other overload to manually provide an OverscrollEffect instance, or disable overscroll.

Parameters

stateThe ScrollableState of the component.
orientationThe Orientation of scrolling.
enabledWhether scrolling is enabled.
flingBehaviorlogic describing fling behavior when drag has finished with velocity. If null, default from ScrollableDefaults.flingBehavior will be used.
reverseScrollingreverses the direction of scrolling. This is useful for experiences where new items appear at the end and the list grows backwards. When reverseScrolling is true, the layout of the content inside the container should also be reversed by the user. For example, in a verticalScroll, setting reverseScrolling true will cause items to be laid out from bottom to top. When using scrollableArea directly in custom list implementations, ensure your layout logic also arranges content in reverse order (e.g. from end to start) to match the scroll behavior.
interactionSourcean optional hoisted MutableInteractionSource for observing and emitting Interactions for this scrollable area. Note that if null is provided, interactions will still happen internally.
bringIntoViewSpecThe configuration that this scrollable area should use to perform scrolling when scroll requests are received from the focus system. If null is provided, the system will use the behavior provided by androidx.compose.foundation.gestures.LocalBringIntoViewSpec which by default has a platform dependent implementation.
Common
fun Modifier.scrollableArea(
    state: ScrollableState,
    orientation: Orientation,
    overscrollEffect: OverscrollEffect?,
    enabled: Boolean = true,
    reverseScrolling: Boolean = false,
    flingBehavior: FlingBehavior? = null,
    interactionSource: MutableInteractionSource? = null,
    bringIntoViewSpec: BringIntoViewSpec? = null,
): Modifier

Configure a component to act as a scrollable area. A scrollable area clips its content to its bounds, renders overscroll, and handles scroll gestures such that the content, not the viewport, moves with the user's gestures.

This modifier is a building block for creating custom scrollable containers, and serves as a higher-level abstraction over androidx.compose.foundation.gestures.scrollable. For simpler use cases, prefer higher-level components that are built with scrollableArea, such as verticalScroll and androidx.compose.foundation.lazy.LazyColumn. For example, verticalScroll offsets the content in the viewport out of the box to have scrollable container behavior.

The primary distinction between scrollable and scrollableArea is in how scroll deltas are handled. scrollableArea inverts the deltas to provide a natural "content-moving" experience. For instance, dragging a finger up results in a positive scroll delta, which accommodates content moving upwards within the layout. In contrast, the lower-level scrollable provides raw, un-inverted deltas, which is useful for custom gesture handling that isn't directly tied to content scrolling.

The direction of scrolling is automatically adjusted based on the orientation, the current androidx.compose.ui.platform.LocalLayoutDirection, and the reverseScrolling flag. Setting reverseScrolling to true is useful for layouts that grow from the end of the container to the beginning, like a chat feed. In such cases, the content within the container should also be laid out in reverse. The following table summarizes the resulting scroll delta for a user's drag gesture:

orientationLayoutDirectionreverseScrollingUser GestureScroll Delta
VerticalLtr and RtlfalseDrag UpPositive
VerticalLtr and RtltrueDrag UpNegative
HorizontalLtrfalseDrag LeftPositive
HorizontalLtrtrueDrag LeftNegative
HorizontalRtlfalseDrag LeftNegative
HorizontalRtltrueDrag LeftPositive

This overload allows providing OverscrollEffect that will be rendered within the scrollable area. See the other overload of scrollableArea in order to use a default OverscrollEffect provided by LocalOverscrollFactory.

Parameters

stateThe ScrollableState of the component.
orientationThe Orientation of scrolling.
overscrollEffectthe OverscrollEffect that will be used to render overscroll for this scrollable area. Note that the OverscrollEffect.node will be applied internally as well - you do not need to use Modifier.overscroll separately.
enabledWhether scrolling is enabled.
flingBehaviorlogic describing fling behavior when drag has finished with velocity. If null, default from ScrollableDefaults.flingBehavior will be used.
reverseScrollingreverses the direction of scrolling. This is useful for experiences where new items appear at the end and the list grows backwards. When reverseScrolling is true, the layout of the content inside the container should also be reversed by the user. For example, in a verticalScroll, setting reverseScrolling true will cause items to be laid out from bottom to top. When using scrollableArea directly in custom list implementations, ensure your layout logic also arranges content in reverse order (e.g. from end to start) to match the scroll behavior.
interactionSourcean optional hoisted MutableInteractionSource for observing and emitting Interactions for this scrollable area. Note that if null is provided, interactions will still happen internally.
bringIntoViewSpecThe configuration that this scrollable area should use to perform scrolling when scroll requests are received from the focus system. If null is provided, the system will use the behavior provided by androidx.compose.foundation.gestures.LocalBringIntoViewSpec which by default has a platform dependent implementation.

Code Examples

ScrollableAreaSample

@Composable
fun ScrollableAreaSample() {
    // This sample demonstrates how to create custom scrollable containers using the scrollableArea
    // modifier.
    // This ScrollableAreaSampleScrollState holds the scroll position and other relevant
    // information. It implements the ScrollableState interface, making it compatible with the
    // scrollableArea modifier, and is similar in function to the ScrollState used with
    // Modifier.verticalScroll.
    val scrollState = rememberScrollableAreaSampleScrollState()
    // For lists with many items, consider using a LazyLayout instead
    Layout(
        modifier =
            Modifier.size(150.dp)
                .scrollableArea(scrollState, Orientation.Vertical)
                .background(Color.LightGray),
        content = {
            repeat(40) {
                Text(
                    modifier = Modifier.padding(vertical = 2.dp),
                    text = "Item $it",
                    fontSize = 24.sp,
                    textAlign = TextAlign.Center,
                )
            }
        },
    ) { measurables, constraints ->
        var totalHeight = 0
        val childConstraints = constraints.copy(minWidth = 0, minHeight = 0)
        val placeables =
            measurables.map { measurable ->
                val placeable = measurable.measure(childConstraints)
                totalHeight += placeable.height
                placeable
            }
        val viewportHeight = constraints.maxHeight
        // 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.
                val itemCenterY = placeableLayoutPositionY + placeable.height / 2
                val distanceFromCenter = abs(itemCenterY - viewportCenterY)
                val normalizedDistance =
                    (distanceFromCenter / (viewportHeight / 2f)).fastCoerceIn(0f, 1f)
                // Items scale between 0.4 at the edges of the viewport and 1 at the center.
                val scaleFactor = 1f - (normalizedDistance * 0.6f)
                // 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
            }
        }
    }
}