Scroll field

A vertical wheel that snaps to one value, for times and other short cyclic ranges.

When to use

ScrollField shows a wheel of values that the user flings or drags. The value in the middle is the selected one. The default item renders the index as a number with two digits, which suits hours and minutes.

The wheel always wraps around: after the last item the first one follows. Use it for cyclic values such as time, and a slider or a text field for open ranges.

In practice

API details and pitfalls of this Material3 version that the design guidelines do not cover.

Playground

The interactive page has a playground with live property controls for this component and generated code.

Examples

Time

Two fields for hour and minute, with labels for screen readers.

val hours = rememberScrollFieldState(itemCount = 24, index = 7)
val minutes = rememberScrollFieldState(itemCount = 60, index = 30)

Column(horizontalAlignment = Alignment.CenterHorizontally) {
    Row(
        horizontalArrangement = Arrangement.spacedBy(8.dp),
        verticalAlignment = Alignment.CenterVertically,
    ) {
        ScrollField(
            state = hours,
            modifier = Modifier
                .size(width = 112.dp, height = ScrollFieldDefaults.ScrollFieldHeight)
                .semantics {
                    contentDescription = "Hour"
                    stateDescription = "${hours.selectedOption}"
                },
        )
        Text(
            ":",
            style = MaterialTheme.typography.displayLarge,
            modifier = Modifier.semantics { hideFromAccessibility() },
        )
        ScrollField(
            state = minutes,
            modifier = Modifier
                .size(width = 112.dp, height = ScrollFieldDefaults.ScrollFieldHeight)
                .semantics {
                    contentDescription = "Minute"
                    stateDescription = "${minutes.selectedOption}"
                },
        )
    }
    Text("Alarm at ${hours.selectedOption}:${minutes.selectedOption.toString().padStart(2, '0')}")
}

Custom items

The field lambda renders anything, here units of volume.

val units = listOf("ml", "cl", "l", "oz", "cup")
val state = rememberScrollFieldState(itemCount = units.size, index = 2)
val colors = ScrollFieldDefaults.colors()

ScrollField(
    state = state,
    modifier = Modifier
        .size(width = 140.dp, height = ScrollFieldDefaults.ScrollFieldHeight)
        .semantics {
            contentDescription = "Unit"
            stateDescription = units[state.selectedOption]
        },
    colors = colors,
    // Any content works. The field gets the index and whether it is the selected one.
    field = { index, selected ->
        Text(
            units[index],
            style = if (selected) MaterialTheme.typography.headlineLargeEmphasized else MaterialTheme.typography.headlineSmall,
            color = if (selected) colors.selectedContentColor else colors.unselectedContentColor,
        )
    },
)

Parameters

NameTypeDefaultDescription
stateScrollFieldStaterememberScrollFieldState(itemCount, index). selectedOption reads the selected index.
modifierModifierModifierSize the field here. The default item needs about 112 dp of width.
colorsScrollFieldColorsScrollFieldDefaults.colors()containerColor, unselectedContentColor and selectedContentColor.
field@Composable (index: Int, selected: Boolean) -> UnitScrollFieldDefaults.ItemContent of one item.
scrollToOption / animateScrollToOptionsuspend (Int) -> UnitScrollFieldState. Jump or animate to an index from code.

Guidelines

A summary. The full design guidelines are at https://m3.material.io/components.

Do:

Don't:

Accessibility

Related components