Build apps faster with over 150+ styled components and screens! Check it out →

Slider

Common

Component in Material 3 Compose

Sliders allow users to make selections from a range of values.

It uses [SliderDefaults.Thumb] and [SliderDefaults.Track] as the thumb and track.

Sliders reflect a range of values along a horizontal bar, from which users may select a single value. They are ideal for adjusting settings such as volume, brightness, or applying image filters.

Sliders
image

Last updated:

Installation

dependencies {
   implementation("androidx.compose.material3:material3:1.4.0-alpha07")
}

Overloads

@OptIn(ExperimentalMaterial3Api::class)
@Composable
fun Slider(
    value: Float,
    onValueChange: (Float) -> Unit,
    modifier: Modifier = Modifier,
    enabled: Boolean = true,
    valueRange: ClosedFloatingPointRange<Float> = 0f..1f,
    @IntRange(from = 0) steps: Int = 0,
    onValueChangeFinished: (() -> Unit)? = null,
    colors: SliderColors = SliderDefaults.colors(),
    interactionSource: MutableInteractionSource = remember { MutableInteractionSource() }
)

Parameters

namedescription
valuecurrent value of the slider. If outside of [valueRange] provided, value will be coerced to this range.
onValueChangecallback in which value should be updated
modifierthe [Modifier] to be applied to this slider
enabledcontrols the enabled state of this slider. When false, this component will not respond to user input, and it will appear visually disabled and disabled to accessibility services.
valueRangerange of values that this slider can take. The passed [value] will be coerced to this range.
stepsif positive, specifies the amount of discrete allowable values between the endpoints of [valueRange]. For example, a range from 0 to 10 with 4 [steps] allows 4 values evenly distributed between 0 and 10 (i.e., 2, 4, 6, 8). If [steps] is 0, the slider will behave continuously and allow any value from the range. Must not be negative.
onValueChangeFinishedcalled when value change has ended. This should not be used to update the slider value (use [onValueChange] instead), but rather to know when the user has completed selecting a new value by ending a drag or a click.
colors[SliderColors] that will be used to resolve the colors used for this slider in different states. See [SliderDefaults.colors].
interactionSourcethe [MutableInteractionSource] representing the stream of [Interaction]s for this slider. You can create and pass in your own remembered instance to observe [Interaction]s and customize the appearance / behavior of this slider in different states.
@Composable
@ExperimentalMaterial3Api
fun Slider(
    value: Float,
    onValueChange: (Float) -> Unit,
    modifier: Modifier = Modifier,
    enabled: Boolean = true,
    onValueChangeFinished: (() -> Unit)? = null,
    colors: SliderColors = SliderDefaults.colors(),
    interactionSource: MutableInteractionSource = remember { MutableInteractionSource() },
    @IntRange(from = 0) steps: Int = 0,
    thumb: @Composable (SliderState) -> Unit = {
        SliderDefaults.Thumb(
            interactionSource = interactionSource,
            colors = colors,
            enabled = enabled
        )
    },
    track: @Composable (SliderState) -> Unit = { sliderState ->
        SliderDefaults.Track(colors = colors, enabled = enabled, sliderState = sliderState)
    },
    valueRange: ClosedFloatingPointRange<Float> = 0f..1f
)

Parameters

namedescription
valuecurrent value of the slider. If outside of [valueRange] provided, value will be coerced to this range.
onValueChangecallback in which value should be updated
modifierthe [Modifier] to be applied to this slider
enabledcontrols the enabled state of this slider. When false, this component will not respond to user input, and it will appear visually disabled and disabled to accessibility services.
onValueChangeFinishedcalled when value change has ended. This should not be used to update the slider value (use [onValueChange] instead), but rather to know when the user has completed selecting a new value by ending a drag or a click.
colors[SliderColors] that will be used to resolve the colors used for this slider in different states. See [SliderDefaults.colors].
interactionSourcethe [MutableInteractionSource] representing the stream of [Interaction]s for this slider. You can create and pass in your own remembered instance to observe [Interaction]s and customize the appearance / behavior of this slider in different states.
stepsif positive, specifies the amount of discrete allowable values between the endpoints of [valueRange]. For example, a range from 0 to 10 with 4 [steps] allows 4 values evenly distributed between 0 and 10 (i.e., 2, 4, 6, 8). If [steps] is 0, the slider will behave continuously and allow any value from the range. Must not be negative.
thumbthe thumb to be displayed on the slider, it is placed on top of the track. The lambda receives a [SliderState] which is used to obtain the current active track.
trackthe track to be displayed on the slider, it is placed underneath the thumb. The lambda receives a [SliderState] which is used to obtain the current active track.
valueRangerange of values that this slider can take. The passed [value] will be coerced to this range.
@Composable
@ExperimentalMaterial3Api
fun Slider(
    state: SliderState,
    modifier: Modifier = Modifier,
    enabled: Boolean = true,
    colors: SliderColors = SliderDefaults.colors(),
    interactionSource: MutableInteractionSource = remember { MutableInteractionSource() },
    thumb: @Composable (SliderState) -> Unit = {
        SliderDefaults.Thumb(
            interactionSource = interactionSource,
            colors = colors,
            enabled = enabled
        )
    },
    track: @Composable (SliderState) -> Unit = { sliderState ->
        SliderDefaults.Track(colors = colors, enabled = enabled, sliderState = sliderState)
    }
)

Parameters

namedescription
state[SliderState] which contains the slider's current value.
modifierthe [Modifier] to be applied to this slider
enabledcontrols the enabled state of this slider. When false, this component will not respond to user input, and it will appear visually disabled and disabled to accessibility services.
colors[SliderColors] that will be used to resolve the colors used for this slider in different states. See [SliderDefaults.colors].
interactionSourcethe [MutableInteractionSource] representing the stream of [Interaction]s for this slider. You can create and pass in your own remembered instance to observe [Interaction]s and customize the appearance / behavior of this slider in different states.
thumbthe thumb to be displayed on the slider, it is placed on top of the track. The lambda receives a [SliderState] which is used to obtain the current active track.
trackthe track to be displayed on the slider, it is placed underneath the thumb. The lambda receives a [SliderState] which is used to obtain the current active track.

Code Examples

SliderSample

@Preview
@Composable
fun SliderSample() {
    var sliderPosition by remember { mutableStateOf(0f) }
    Column(modifier = Modifier.padding(horizontal = 16.dp)) {
        Text(text = "%.2f".format(sliderPosition))
        Slider(
            modifier = Modifier.semantics { contentDescription = "Localized Description" },
            value = sliderPosition,
            onValueChange = { sliderPosition = it }
        )
    }
}

StepsSliderSample

@Preview
@Composable
fun StepsSliderSample() {
    var sliderPosition by remember { mutableStateOf(0f) }
    Column(modifier = Modifier.padding(horizontal = 16.dp)) {
        Text(text = sliderPosition.roundToInt().toString())
        Slider(
            modifier = Modifier.semantics { contentDescription = "Localized Description" },
            value = sliderPosition,
            onValueChange = { sliderPosition = it },
            valueRange = 0f..100f,
            onValueChangeFinished = {
                // launch some business logic update with the state you hold
                // viewModel.updateSelectedSliderValue(sliderPosition)
            },
            // Only allow multiples of 10. Excluding the endpoints of `valueRange`,
            // there are 9 steps (10, 20, ..., 90).
            steps = 9
        )
    }
}

SliderWithCustomThumbSample

@OptIn(ExperimentalMaterial3Api::class)
@Preview
@Composable
fun SliderWithCustomThumbSample() {
    var sliderPosition by remember { mutableStateOf(0f) }
    val interactionSource: MutableInteractionSource = remember { MutableInteractionSource() }
    Column(modifier = Modifier.padding(horizontal = 16.dp)) {
        Slider(
            modifier = Modifier.semantics { contentDescription = "Localized Description" },
            value = sliderPosition,
            onValueChange = { sliderPosition = it },
            valueRange = 0f..100f,
            interactionSource = interactionSource,
            onValueChangeFinished = {
                // launch some business logic update with the state you hold
                // viewModel.updateSelectedSliderValue(sliderPosition)
            },
            thumb = {
                Label(
                    label = {
                        PlainTooltip(modifier = Modifier.sizeIn(45.dp, 25.dp).wrapContentWidth()) {
                            Text("%.2f".format(sliderPosition))
                        }
                    },
                    interactionSource = interactionSource
                ) {
                    Icon(
                        imageVector = Icons.Filled.Favorite,
                        contentDescription = null,
                        modifier = Modifier.size(ButtonDefaults.IconSize),
                        tint = Color.Red
                    )
                }
            }
        )
    }
}

SliderWithCustomTrackAndThumbSample

@OptIn(ExperimentalMaterial3Api::class)
@Preview
@Composable
fun SliderWithCustomTrackAndThumbSample() {
    val sliderState = remember {
        SliderState(
            valueRange = 0f..100f,
            onValueChangeFinished = {
                // launch some business logic update with the state you hold
                // viewModel.updateSelectedSliderValue(sliderPosition)
            }
        )
    }
    val interactionSource = remember { MutableInteractionSource() }
    val colors = SliderDefaults.colors(thumbColor = Color.Red, activeTrackColor = Color.Red)
    Column(modifier = Modifier.padding(horizontal = 16.dp)) {
        Text(text = "%.2f".format(sliderState.value))
        Slider(
            state = sliderState,
            modifier = Modifier.semantics { contentDescription = "Localized Description" },
            interactionSource = interactionSource,
            thumb = {
                SliderDefaults.Thumb(interactionSource = interactionSource, colors = colors)
            },
            track = { SliderDefaults.Track(colors = colors, sliderState = sliderState) }
        )
    }
}

SliderWithTrackIconsSample

@OptIn(ExperimentalMaterial3Api::class, ExperimentalMaterial3ExpressiveApi::class)
@Preview
@Composable
fun SliderWithTrackIconsSample() {
    val sliderState = remember {
        SliderState(
            valueRange = 0f..100f,
            onValueChangeFinished = {
                // launch some business logic update with the state you hold
                // viewModel.updateSelectedSliderValue(sliderPosition)
            }
        )
    }
    val interactionSource = remember { MutableInteractionSource() }
    val startIcon = rememberVectorPainter(Icons.Filled.MusicNote)
    val endIcon = rememberVectorPainter(Icons.Filled.MusicOff)
    Column(modifier = Modifier.padding(horizontal = 16.dp)) {
        Text(text = "%.2f".format(sliderState.value))
        Slider(
            state = sliderState,
            modifier = Modifier.semantics { contentDescription = "Localized Description" },
            interactionSource = interactionSource,
            track = {
                val iconSize = DpSize(20.dp, 20.dp)
                val iconPadding = 10.dp
                val thumbTrackGapSize = 6.dp
                val activeIconColor = SliderDefaults.colors().activeTickColor
                val inactiveIconColor = SliderDefaults.colors().inactiveTickColor
                val trackIconStart: DrawScope.(Offset, Color) -> Unit = { offset, color ->
                    translate(offset.x + iconPadding.toPx(), offset.y) {
                        with(startIcon) {
                            draw(iconSize.toSize(), colorFilter = ColorFilter.tint(color))
                        }
                    }
                }
                val trackIconEnd: DrawScope.(Offset, Color) -> Unit = { offset, color ->
                    translate(offset.x - iconPadding.toPx() - iconSize.toSize().width, offset.y) {
                        with(endIcon) {
                            draw(iconSize.toSize(), colorFilter = ColorFilter.tint(color))
                        }
                    }
                }
                SliderDefaults.Track(
                    sliderState = sliderState,
                    modifier =
                        Modifier.height(36.dp).drawWithContent {
                            drawContent()

                            val yOffset = size.height / 2 - iconSize.toSize().height / 2
                            val activeTrackStart = 0f
                            val activeTrackEnd =
                                size.width * sliderState.coercedValueAsFraction -
                                    thumbTrackGapSize.toPx()
                            val inactiveTrackStart = activeTrackEnd + thumbTrackGapSize.toPx() * 2
                            val inactiveTrackEnd = size.width

                            val activeTrackWidth = activeTrackEnd - activeTrackStart
                            val inactiveTrackWidth = inactiveTrackEnd - inactiveTrackStart
                            if (
                                iconSize.toSize().width < activeTrackWidth - iconPadding.toPx() * 2
                            ) {
                                trackIconStart(Offset(activeTrackStart, yOffset), activeIconColor)
                                trackIconEnd(Offset(activeTrackEnd, yOffset), activeIconColor)
                            }
                            if (
                                iconSize.toSize().width <
                                    inactiveTrackWidth - iconPadding.toPx() * 2
                            ) {
                                trackIconStart(
                                    Offset(inactiveTrackStart, yOffset),
                                    inactiveIconColor
                                )
                                trackIconEnd(Offset(inactiveTrackEnd, yOffset), inactiveIconColor)
                            }
                        },
                    trackCornerSize = 12.dp,
                    drawStopIndicator = null,
                    thumbTrackGapSize = thumbTrackGapSize
                )
            }
        )
    }
}
by @alexstyl