
The Jetpack Compose Slider is one of the most practical and interactive input components you can add to any Android app. Whether you're building a volume control, a price filter, or a settings screen, sliders give users a smooth way to select a value within a range. If you've been working with the XML-based SeekBar before, you'll immediately appreciate how much simpler and more expressive the composable approach is. In this guide you'll learn everything you need about implementing and customizing a Slider in Jetpack Compose, from the most basic setup all the way to range sliders and dynamic value display.
A Slider is a composable provided by the Material3 library that lets users select a value by dragging a thumb along a track. It works well for scenarios where typing a precise number from a keyboard isn't practical — think brightness settings, playback speed selectors, or budget range filters.
Jetpack Compose makes building a Slider straightforward because everything is state-driven. You define a state variable to hold the current value, pass it to the Slider composable, and update it through the onValueChange callback. There are no listeners to register or XML attributes to configure — just a composable function and a piece of state.
The official Slider API reference covers the full parameter list if you want to explore every option.
The simplest Compose Slider you can build needs just two things: a state variable holding the current float value, and an onValueChange lambda that updates that state whenever the user drags. By default, the value range runs from 0.0 to 1.0.
Here's a minimal working example that displays a Slider and shows the selected value as text above it:
import android.os.Bundle
import androidx.activity.ComponentActivity
import androidx.activity.compose.setContent
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.padding
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Slider
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.Modifier
import androidx.compose.ui.unit.dp
class MainActivity : ComponentActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContent {
MaterialTheme {
Surface {
BasicSliderDemo()
}
}
}
}
}
@Composable
fun BasicSliderDemo() {
var sliderValue by remember { mutableStateOf(0.5f) }
Column(modifier = Modifier.padding(24.dp)) {
Text(text = "Selected: %.2f".format(sliderValue))
Spacer(modifier = Modifier.height(12.dp))
Slider(
value = sliderValue,
onValueChange = { sliderValue = it },
modifier = Modifier.fillMaxWidth()
)
}
}
When you run this, you'll see a horizontal track with a draggable thumb. As you slide it, the text above updates in real time showing a value between 0.00 and 1.00. The state drives the UI — this is the core mental model of Jetpack Compose. Notice how there's no imperative "set this view's value" call anywhere; the composable re-renders automatically when the state changes.
Output:
Selected: 0.50
[==========●==========] (thumb centered on the track)
The default 0.0 to 1.0 range is rarely what you need in a real app. The valueRange parameter accepts any closed floating-point range, so you can define exactly the numbers that make sense for your use case. Want a volume slider that goes from 0 to 100? Or a temperature selector spanning -20 to 50 degrees? Just supply the appropriate range.
import android.os.Bundle
import androidx.activity.ComponentActivity
import androidx.activity.compose.setContent
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.padding
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Slider
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.Modifier
import androidx.compose.ui.unit.dp
class MainActivity : ComponentActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContent {
MaterialTheme {
Surface {
VolumeSlider()
}
}
}
}
}
@Composable
fun VolumeSlider() {
var volume by remember { mutableStateOf(50f) }
Column(modifier = Modifier.padding(24.dp)) {
Text(text = "Volume: ${volume.toInt()}")
Spacer(modifier = Modifier.height(12.dp))
Slider(
value = volume,
onValueChange = { volume = it },
valueRange = 0f..100f,
modifier = Modifier.fillMaxWidth()
)
}
}
The state variable is initialized to 50, so the thumb starts centered. As the user drags, the volume integer updates live. The Slider composable maps your raw float to the correct position on the track automatically — you just think in your domain units.
Output:
Volume: 50
[==========●==========] (thumb centered, range 0 to 100)
A continuous slider lets users pick any value within the range. A discrete slider, on the other hand, snaps to fixed positions — useful for things like star ratings, font size multipliers, or item quantity selectors. You enable this by setting the steps parameter, which defines how many intermediate stop points exist between the start and end of the range.
If you set steps to 3 with a range of 1 to 5, the slider snaps to five total positions: 1, 2, 3, 4, and 5. The formula is: total positions = steps + 2. Small tick marks appear on the track at each position automatically — Material3 handles that visual detail for you.
import android.os.Bundle
import androidx.activity.ComponentActivity
import androidx.activity.compose.setContent
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.padding
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Slider
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.Modifier
import androidx.compose.ui.unit.dp
class MainActivity : ComponentActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContent {
MaterialTheme {
Surface {
StepSliderDemo()
}
}
}
}
}
@Composable
fun StepSliderDemo() {
var rating by remember { mutableStateOf(3f) }
Column(modifier = Modifier.padding(24.dp)) {
Text(text = "Rating: ${rating.toInt()} / 5")
Spacer(modifier = Modifier.height(12.dp))
Slider(
value = rating,
onValueChange = { rating = it },
valueRange = 1f..5f,
steps = 3,
modifier = Modifier.fillMaxWidth()
)
}
}
Drag the thumb and you'll feel it snap cleanly to each whole number instead of sliding freely. The tick marks on the track give users a visual hint that this is a stepped, discrete input — a subtle but important UX signal.
Output:
Rating: 3 / 5
[====|====●====|====] (tick marks visible, thumb snapped to position 3)
Material3's composable slider uses your app's theme colors by default, but you can override them precisely using the SliderDefaults.colors() function. This lets you independently set the active track color (the filled portion behind the thumb), the inactive track color (the unfilled portion ahead of the thumb), and the thumb color itself.
This is especially useful for branded UI where your design system has specific color requirements, or for screens where the slider needs to communicate meaning through color — like red for danger zones or green for safe ranges.
import android.os.Bundle
import androidx.activity.ComponentActivity
import androidx.activity.compose.setContent
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.padding
import androidx.compose.material3.MaterialTheme
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.Modifier
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.unit.dp
class MainActivity : ComponentActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContent {
MaterialTheme {
Surface {
ColoredSliderDemo()
}
}
}
}
}
@Composable
fun ColoredSliderDemo() {
var brightness by remember { mutableStateOf(0.7f) }
Column(modifier = Modifier.padding(24.dp)) {
Text(text = "Brightness: ${(brightness * 100).toInt()}%")
Spacer(modifier = Modifier.height(12.dp))
Slider(
value = brightness,
onValueChange = { brightness = it },
colors = SliderDefaults.colors(
thumbColor = Color(0xFFFFA500),
activeTrackColor = Color(0xFFFFA500),
inactiveTrackColor = Color(0xFFFFDEAD)
),
modifier = Modifier.fillMaxWidth()
)
}
}
The active track and thumb are now vivid orange, while the unfilled track behind the thumb shows a soft peach. Every detail of the Android Compose Slider appearance is accessible through this single colors parameter — no custom drawing required.
Output:
Brightness: 70%
[██████████●░░░░░] (orange filled track, orange thumb, peach inactive portion)
The RangeSlider composable is a variation that gives users two thumbs on the same track — one for a minimum value and one for a maximum. This makes it ideal for price filters, date range selectors, or any scenario where your user needs to specify both a lower and upper bound simultaneously.
Instead of a single float for state, you maintain a ClosedFloatingPointRange. The start property represents the left thumb and endInclusive represents the right thumb. Note that RangeSlider is currently part of the experimental Material3 API, so you'll need the @OptIn annotation.
import android.os.Bundle
import androidx.activity.ComponentActivity
import androidx.activity.compose.setContent
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.padding
import androidx.compose.material3.ExperimentalMaterial3Api
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.RangeSlider
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.Modifier
import androidx.compose.ui.unit.dp
class MainActivity : ComponentActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContent {
MaterialTheme {
Surface {
PriceRangeSlider()
}
}
}
}
}
@OptIn(ExperimentalMaterial3Api::class)
@Composable
fun PriceRangeSlider() {
var priceRange by remember { mutableStateOf(20f..80f) }
Column(modifier = Modifier.padding(24.dp)) {
Text(
text = "$${priceRange.start.toInt()} – $${priceRange.endInclusive.toInt()}"
)
Spacer(modifier = Modifier.height(12.dp))
RangeSlider(
value = priceRange,
onValueChange = { priceRange = it },
valueRange = 0f..100f,
modifier = Modifier.fillMaxWidth()
)
}
}
The left thumb controls the minimum price and the right thumb controls the maximum. The track between the two thumbs is highlighted, giving users an immediate visual sense of the selected window. The text display updates live as either thumb moves.
Output:
$20 – $80
[====●==================●====] (two thumbs, highlighted segment between $20 and $80)
Sometimes you don't want to trigger a side effect on every single frame of a drag gesture — you only want to act once the user lifts their finger and settles on a final value. The onValueChangeFinished callback is built exactly for this. It fires once when the user releases the thumb, regardless of how many intermediate positions were crossed during the drag.
This pattern is common when a Jetpack Compose Slider controls something expensive to update — like triggering a network request for filtered results, adjusting audio output, or logging an analytics event. Firing those on every pixel of movement would be wasteful or even destructive.
import android.os.Bundle
import androidx.activity.ComponentActivity
import androidx.activity.compose.setContent
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.padding
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Slider
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.Modifier
import androidx.compose.ui.unit.dp
class MainActivity : ComponentActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContent {
MaterialTheme {
Surface {
FinishedSliderDemo()
}
}
}
}
}
@Composable
fun FinishedSliderDemo() {
var liveValue by remember { mutableStateOf(0.5f) }
var committedValue by remember { mutableStateOf(0.5f) }
Column(modifier = Modifier.padding(24.dp)) {
Text(text = "Dragging: %.2f".format(liveValue))
Text(text = "Committed: %.2f".format(committedValue))
Spacer(modifier = Modifier.height(12.dp))
Slider(
value = liveValue,
onValueChange = { liveValue = it },
onValueChangeFinished = { committedValue = liveValue },
modifier = Modifier.fillMaxWidth()
)
}
}
While you're dragging, "Dragging" updates continuously with every position change. The "Committed" value stays frozen at 0.50 until you release the thumb, at which point it snaps to match the final settled value. This separation between live preview and final commit is a clean and widely used pattern when building form-heavy screens with Compose Slider.
Output:
Dragging: 0.73
Committed: 0.50
[==============●======] (thumb mid-drag — committed value still shows last released position)
This final example brings together everything covered: a labeled continuous Slider with custom colors, a discrete Slider with steps, and a two-thumb RangeSlider — all in one screen with live value display. Copy this into an Android project with Material3 and Jetpack Compose dependencies configured to run it immediately.
import android.os.Bundle
import androidx.activity.ComponentActivity
import androidx.activity.compose.setContent
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.padding
import androidx.compose.material3.ExperimentalMaterial3Api
import androidx.compose.material3.HorizontalDivider
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.Modifier
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp
class MainActivity : ComponentActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContent {
MaterialTheme {
Surface(modifier = Modifier.fillMaxSize()) {
SliderShowcase()
}
}
}
}
}
@OptIn(ExperimentalMaterial3Api::class)
@Composable
fun SliderShowcase() {
var volumeLevel by remember { mutableStateOf(60f) }
var fontSizeStep by remember { mutableStateOf(3f) }
var budgetRange by remember { mutableStateOf(200f..700f) }
Column(
modifier = Modifier
.padding(horizontal = 24.dp, vertical = 32.dp)
.fillMaxWidth()
) {
Text(text = "Volume Control", fontWeight = FontWeight.Bold, fontSize = 16.sp)
Spacer(modifier = Modifier.height(4.dp))
Text(text = "Level: ${volumeLevel.toInt()}%")
Spacer(modifier = Modifier.height(8.dp))
Slider(
value = volumeLevel,
onValueChange = { volumeLevel = it },
valueRange = 0f..100f,
colors = SliderDefaults.colors(
thumbColor = Color(0xFF6200EE),
activeTrackColor = Color(0xFF6200EE),
inactiveTrackColor = Color(0xFFBB86FC)
),
modifier = Modifier.fillMaxWidth()
)
Spacer(modifier = Modifier.height(24.dp))
HorizontalDivider()
Spacer(modifier = Modifier.height(24.dp))
Text(text = "Font Size Picker", fontWeight = FontWeight.Bold, fontSize = 16.sp)
Spacer(modifier = Modifier.height(4.dp))
Text(text = "Size: ${fontSizeStep.toInt()}x")
Spacer(modifier = Modifier.height(8.dp))
Slider(
value = fontSizeStep,
onValueChange = { fontSizeStep = it },
valueRange = 1f..5f,
steps = 3,
modifier = Modifier.fillMaxWidth()
)
Spacer(modifier = Modifier.height(24.dp))
HorizontalDivider()
Spacer(modifier = Modifier.height(24.dp))
Text(text = "Budget Range Filter", fontWeight = FontWeight.Bold, fontSize = 16.sp)
Spacer(modifier = Modifier.height(4.dp))
Text(
text = "$${budgetRange.start.toInt()} – $${budgetRange.endInclusive.toInt()}"
)
Spacer(modifier = Modifier.height(8.dp))
RangeSlider(
value = budgetRange,
onValueChange = { budgetRange = it },
valueRange = 0f..1000f,
modifier = Modifier.fillMaxWidth()
)
}
}