
In this article, we’ll explore Material 3 Slider in Jetpack Compose.
Prerequisites:
This article talks about the Material 3 slider. If you are looking for the Material 2 version, follow this link.
What is Slider in Android?
Sliders allow users to make selections from a range of values. They are useful for adjusting settings such as volume and brightness, or applying image filters.
Material 3 Slider Anatomy:
- Active track
- Active tick marks
- Label text
- Label container
- Handle
- State layer
- Inactive track
- Inactive tick marks
There are two types of slider APIs:
1. Slider:
Users can select only one value in a given range.
Example:

2. RangeSlider:
Users can select two values in a given range.
Example:

Let’s implement these sliders in Android Studio.
First, create an empty Compose project and open MainActivity.kt. Create a MyUI() composable and call it from the onCreate() method. We’ll write our code in it.
// add the following packages
import android.os.Bundle
import android.widget.Toast
import androidx.activity.ComponentActivity
import androidx.activity.compose.setContent
import androidx.compose.foundation.hoverable
import androidx.compose.foundation.indication
import androidx.compose.foundation.interaction.MutableInteractionSource
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.size
import androidx.compose.foundation.layout.width
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.Favorite
import androidx.compose.material.ripple.rememberRipple
import androidx.compose.material3.ExperimentalMaterial3Api
import androidx.compose.material3.Icon
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.RangeSlider
import androidx.compose.material3.Slider
import androidx.compose.material3.SliderDefaults
import androidx.compose.material3.Surface
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.setValue
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.graphics.TransformOrigin
import androidx.compose.ui.graphics.graphicsLayer
import androidx.compose.ui.layout.layout
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.unit.Constraints
import androidx.compose.ui.unit.Dp
import androidx.compose.ui.unit.dp
class MainActivity : ComponentActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContent {
YourProjectNameTheme {
Surface(
modifier = Modifier.fillMaxSize(),
color = MaterialTheme.colorScheme.background
) {
Column(
modifier = Modifier
.fillMaxSize()
.padding(horizontal = 8.dp),
verticalArrangement = Arrangement.Center,
horizontalAlignment = Alignment.CenterHorizontally
) {
MyUI()
}
}
}
}
}
}
@Composable
fun MyUI() {
}
The Slider() composable looks like this:
@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() }
)
value – The current value of the slider.
onValueChange – It is a lambda that gets called whenever the slider value is changed.
modifier – It is to change the layout of the slider.
enabled – If the slider is enabled or not. If it is false, the slider won’t respond to user clicks.
valueRange – The range of values that this slider can take.
steps – It indicates if the slider specifies continuous or discrete values. Any value higher than 0 denotes discrete, while 0 denotes continuous. It should not be negative.
onValueChangeFinished – It is called when the value change has been finished (i.e. when the user has completed selecting a new value by ending a drag or a click).
colors – The slider colors at different states.
interactionSource -It helps us to observe and customize the interactions. For example, you can disable the ripple effect.
Material 3 Jetpack Compose provides two additional overloads with thumb and track parameters. They are used to create a custom slider.
1. Slider with thumb parameter:
@Composable
@ExperimentalMaterial3Api
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() },
thumb: @Composable (SliderPositions) -> Unit // thumb parameter
)
2. Slider with thumb and track parameters:
@Composable
@ExperimentalMaterial3Api
fun Slider(
value: Float,
onValueChange: (Float) -> Unit,
track: @Composable (SliderPositions) -> Unit, // track parameter
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() },
thumb: @Composable (SliderPositions) -> Unit = // thumb parameter
remember(interactionSource, colors, enabled) { {
SliderDefaults.Thumb(
interactionSource = interactionSource,
colors = colors,
enabled = enabled
)
} }
)
Slider Example:
The value and onValueChange are mandatory parameters.
@Composable
fun MyUI() {
val contextForToast = LocalContext.current.applicationContext
var sliderValue by remember {
mutableStateOf(0f) // pass the initial value
}
Column(horizontalAlignment = Alignment.CenterHorizontally) {
Slider(
value = sliderValue,
onValueChange = { newValue ->
sliderValue = newValue
},
onValueChangeFinished = {
Toast.makeText(contextForToast, "sliderValue = $sliderValue", Toast.LENGTH_SHORT)
.show()
},
valueRange = 1f..10f,
steps = 0
)
Text(text = "sliderValue = $sliderValue")
}
}
Output:

Slider Thumb and Track Colors:
Jetpack Compose provides SliderDefaults.colors() method. We can change almost any color in any state.

Let us change the thumb and active track colors.
@Composable
fun MyUI() {
val contextForToast = LocalContext.current.applicationContext
var sliderValue by remember {
mutableStateOf(0f) // pass the initial value
}
Column(horizontalAlignment = Alignment.CenterHorizontally) {
Slider(
value = sliderValue,
onValueChange = { newValue ->
sliderValue = newValue
},
onValueChangeFinished = {
Toast.makeText(contextForToast, "sliderValue = $sliderValue", Toast.LENGTH_SHORT)
.show()
},
valueRange = 1f..10f,
steps = 0,
colors = SliderDefaults.colors(
thumbColor = Color.Magenta,
activeTrackColor = Color.Green
)
)
Text(text = "sliderValue = $sliderValue")
}
}
Output:

Vertical Slider:
We can create a verticle slider using the graphicsLayer and layout modifiers.
@Composable
fun MyUI() {
val contextForToast = LocalContext.current.applicationContext
var sliderValue by remember {
mutableStateOf(0f)
}
Column(horizontalAlignment = Alignment.CenterHorizontally) {
Slider(
modifier = Modifier
.graphicsLayer {
rotationZ = 270f
transformOrigin = TransformOrigin(0f, 0f)
}
.layout { measurable, constraints ->
val placeable = measurable.measure(
Constraints(
minWidth = constraints.minHeight,
maxWidth = constraints.maxHeight,
minHeight = constraints.minWidth,
maxHeight = constraints.maxHeight,
)
)
layout(placeable.height, placeable.width) {
placeable.place(-placeable.width, 0)
}
}
.width(200.dp) // this is the height
.height(50.dp), // this is the width
value = sliderValue,
onValueChange = { newValue ->
sliderValue = newValue
},
onValueChangeFinished = {
Toast.makeText(contextForToast, "sliderValue = $sliderValue", Toast.LENGTH_SHORT)
.show()
},
valueRange = 1f..10f,
steps = 0
)
Text(text = "sliderValue = $sliderValue")
}
}
Output:

Custom Slider:
The thumb and track parameters are used to create custom sliders.
@OptIn(ExperimentalMaterial3Api::class)
@Composable
fun MyUI() {
val contextForToast = LocalContext.current.applicationContext
var sliderValue by remember {
mutableStateOf(0f) // pass the initial value
}
val interactionSource = MutableInteractionSource()
Column(horizontalAlignment = Alignment.CenterHorizontally) {
Slider(
value = sliderValue,
onValueChange = { newValue ->
sliderValue = newValue
},
onValueChangeFinished = {
Toast.makeText(contextForToast, "sliderValue = $sliderValue", Toast.LENGTH_SHORT)
.show()
},
valueRange = 1f..10f,
steps = 0,
thumb = {
Icon(
modifier = Modifier
.size(36.dp)
.indication(
interactionSource = interactionSource,
indication = rememberRipple(
bounded = false,
radius = Dp.Unspecified
)
)
.hoverable(interactionSource = interactionSource),
imageVector = Icons.Default.Favorite,
tint = Color.Red,
contentDescription = null
)
},
colors = SliderDefaults.colors(
activeTrackColor = Color.Red,
inactiveTrackColor = Color.Red.copy(alpha = 0.2f)
)
)
Text(text = "sliderValue = $sliderValue")
}
}
Output:

You could face the following error on the newer versions of Jetpack Compose:
Creating a MutableInteractionSource during composition without using remember
To fix it, replace the interactionSource parameter with the following code:
val interactionSource = remember {
MutableInteractionSource()
}
Discrete Slider:
Discrete sliders represent a numeric value.
Example:

We can create discrete sliders by passing an integer value to the steps parameter. The integer should be greater than 0. It represents the number of tick marks between the start and end values.
Example 1: valueRange = 0, 2, 4…..10.
Here, 0 and 10 are the start and end values. The steps value is the same as the number of tick marks between them. You can use the following formula to calculate it:

steps value = (10 – 0) / 2 – 1 = 4
Example 2: valueRange = 1, 3, 5….15
Start value = 1
End value = 15
Difference between two successive numbers = 2
steps value = (15 – 1) / 2 – 1 = 6
Note: The numbers in the valueRange should be evenly distributed. If not, you get floating values when you tap on the tick marks.
Example:
@Composable
fun MyUI() {
val contextForToast = LocalContext.current.applicationContext
var sliderValue by remember {
mutableStateOf(4f) // pass the initial value
}
Column(horizontalAlignment = Alignment.CenterHorizontally) {
Slider(
value = sliderValue,
onValueChange = { newValue ->
sliderValue = newValue
},
onValueChangeFinished = {
Toast.makeText(contextForToast, "sliderValue = $sliderValue", Toast.LENGTH_SHORT)
.show()
},
valueRange = 0f..10f,
steps = 4
)
Text(text = "sliderValue = $sliderValue")
}
}
Output:

Range Slider:
RangeSlider is similar to the Slider but allows the user to select two values. The two values are within the value range but cannot cross each other.
Example:

RangeSlider() is currently experimental:
@Composable
@ExperimentalMaterial3Api
fun RangeSlider(
value: ClosedFloatingPointRange<Float>,
onValueChange: (ClosedFloatingPointRange<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()
)
Example:
@OptIn(ExperimentalMaterial3Api::class)
@Composable
fun MyUI() {
val contextForToast = LocalContext.current.applicationContext
var sliderValues by remember {
mutableStateOf(5f..13f) // pass the initial values
}
Column(horizontalAlignment = Alignment.CenterHorizontally) {
RangeSlider(
value = sliderValues,
onValueChange = { newValues ->
sliderValues = newValues
},
onValueChangeFinished = {
Toast.makeText(
contextForToast,
"Start: ${sliderValues.start}, End: ${sliderValues.endInclusive}",
Toast.LENGTH_SHORT
).show()
},
valueRange = 1f..20f,
steps = 0
)
Text(text = "Start: ${sliderValues.start}, End: ${sliderValues.endInclusive}")
}
}
Output:

Note: RangeSlider() doesn’t have overloads for customizing the thumb and track.
Discrete Range Slider:
Similar to the discrete slider, we can create a discrete range slider by passing an integer value to the steps parameter.
@OptIn(ExperimentalMaterial3Api::class)
@Composable
fun MyUI() {
val contextForToast = LocalContext.current.applicationContext
var sliderValues by remember {
mutableStateOf(0f..10f) // pass the initial values
}
Column(horizontalAlignment = Alignment.CenterHorizontally) {
RangeSlider(
value = sliderValues,
onValueChange = { newValues ->
sliderValues = newValues
},
onValueChangeFinished = {
Toast.makeText(
contextForToast,
"Start: ${sliderValues.start.toInt()}, End: ${sliderValues.endInclusive.toInt()}",
Toast.LENGTH_SHORT
).show()
},
valueRange = 0f..10f,
steps = 4
)
Text(text = "Start: ${sliderValues.start.toInt()}, End: ${sliderValues.endInclusive.toInt()}")
}
}
Output:

This is all about Material 3 sliders in Jetpack Compose. I hope you have learned something new. If you have any doubts, leave a comment below.
Related Articles:
References: