Skip to main content
Lumo UI provides Slider and RangeSlider components for selecting values from a continuous or discrete range.

Import

import com.nomanr.lumo.ui.components.Slider
import com.nomanr.lumo.ui.components.RangeSlider
import com.nomanr.lumo.ui.components.SliderState
import com.nomanr.lumo.ui.components.RangeSliderState

Slider

A slider for selecting a single value from a range.

Component Signature

@Composable
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,
    valueRange: ClosedFloatingPointRange<Float> = 0f..1f,
)
Source: Slider.kt:38-70

State-based Slider

@Composable
fun Slider(
    state: SliderState,
    modifier: Modifier = Modifier,
    enabled: Boolean = true,
    colors: SliderColors = SliderDefaults.colors(),
    interactionSource: MutableInteractionSource = remember { MutableInteractionSource() },
)
Source: Slider.kt:73-83

RangeSlider

A slider for selecting a range of values (start and end).

Component Signature

@Composable
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(),
    startInteractionSource: MutableInteractionSource = remember { MutableInteractionSource() },
    endInteractionSource: MutableInteractionSource = remember { MutableInteractionSource() },
)
Source: Slider.kt:86-122

Parameters

Slider Parameters

value
Float
required
Current value of the slider
onValueChange
(Float) -> Unit
required
Callback invoked when the value changes during dragging
modifier
Modifier
default:"Modifier"
Modifier to be applied to the slider
enabled
Boolean
default:"true"
Whether the slider is enabled and can be interacted with
onValueChangeFinished
(() -> Unit)?
default:"null"
Callback invoked when the user finishes adjusting the slider (on release)
valueRange
ClosedFloatingPointRange<Float>
default:"0f..1f"
Range of values the slider can take
steps
Int
default:"0"
Number of discrete steps between min and max. 0 means continuous
colors
SliderColors
default:"SliderDefaults.colors()"
Colors for different slider states and parts

RangeSlider Parameters

value
ClosedFloatingPointRange<Float>
required
Current range (start..end) of the slider
onValueChange
(ClosedFloatingPointRange<Float>) -> Unit
required
Callback invoked when either start or end value changes
startInteractionSource
MutableInteractionSource
Interaction source for the start thumb
endInteractionSource
MutableInteractionSource
Interaction source for the end thumb

Examples

Basic Slider

From preview (Slider.kt:191-202):
var value by remember { mutableFloatStateOf(0.5f) }

Slider(
    value = value,
    onValueChange = { value = it },
    modifier = Modifier.fillMaxWidth()
)

Stepped Slider

From preview (Slider.kt:204-216):
var value by remember { mutableFloatStateOf(0.4f) }

Column {
    Text("Stepped Slider (5 steps)", style = AppTheme.typography.h4)
    Slider(
        value = value,
        onValueChange = { value = it },
        steps = 4, // 4 steps creates 5 positions: 0, 0.25, 0.5, 0.75, 1
        modifier = Modifier.fillMaxWidth()
    )
}

Custom Range Slider

From preview (Slider.kt:218-241):
var value by remember { mutableFloatStateOf(30f) }

Column {
    Text("Custom Range (0-100)", style = AppTheme.typography.h4)
    Row(
        modifier = Modifier.fillMaxWidth(),
        horizontalArrangement = Arrangement.spacedBy(16.dp),
        verticalAlignment = Alignment.CenterVertically
    ) {
        Slider(
            value = value,
            onValueChange = { value = it },
            valueRange = 0f..100f,
            modifier = Modifier.weight(1f)
        )
        Text(
            text = "${value.toInt()}",
            style = AppTheme.typography.body1,
            modifier = Modifier.width(40.dp)
        )
    }
}

Disabled Slider

From preview (Slider.kt:243-261):
Column {
    Text("Disabled States", style = AppTheme.typography.h4)
    
    Slider(
        value = 0.3f,
        onValueChange = {},
        enabled = false,
        modifier = Modifier.fillMaxWidth()
    )
    
    Spacer(Modifier.height(8.dp))
    
    Slider(
        value = 0.7f,
        onValueChange = {},
        enabled = false,
        modifier = Modifier.fillMaxWidth()
    )
}

Interactive Slider with Callbacks

From preview (Slider.kt:282-303):
var value by remember { mutableFloatStateOf(50f) }
var isEditing by remember { mutableStateOf(false) }

Column {
    Text("Interactive Slider", style = AppTheme.typography.h4)
    Text(
        text = if (isEditing) "Editing..." else "Value: ${value.toInt()}",
        style = AppTheme.typography.body1
    )
    Slider(
        value = value,
        onValueChange = {
            value = it
            isEditing = true
        },
        valueRange = 0f..100f,
        onValueChangeFinished = { isEditing = false },
        modifier = Modifier.fillMaxWidth()
    )
}

Custom Colors Slider

From preview (Slider.kt:263-280):
var value by remember { mutableFloatStateOf(0.5f) }

Column {
    Text("Custom Colors", style = AppTheme.typography.h4)
    Slider(
        value = value,
        onValueChange = { value = it },
        colors = SliderDefaults.colors(
            thumbColor = AppTheme.colors.error,
            activeTrackColor = AppTheme.colors.error,
            inactiveTrackColor = AppTheme.colors.error.copy(alpha = 0.3f)
        ),
        modifier = Modifier.fillMaxWidth()
    )
}

Basic RangeSlider

From preview (Slider.kt:326-337):
var range by remember { mutableStateOf(0.2f..0.8f) }

Column {
    Text("Basic Range Slider", style = AppTheme.typography.h4)
    RangeSlider(
        value = range,
        onValueChange = { range = it },
        modifier = Modifier.fillMaxWidth()
    )
}

RangeSlider with Values Display

From preview (Slider.kt:353-379):
var range by remember { mutableStateOf(20f..80f) }

Column {
    Text("Custom Range (0-100)", style = AppTheme.typography.h4)
    Column {
        RangeSlider(
            value = range,
            onValueChange = { range = it },
            valueRange = 0f..100f,
            modifier = Modifier.fillMaxWidth()
        )
        Row(
            modifier = Modifier.fillMaxWidth(),
            horizontalArrangement = Arrangement.SpaceBetween
        ) {
            Text(
                text = "Start: ${range.start.toInt()}",
                style = AppTheme.typography.body1
            )
            Text(
                text = "End: ${range.endInclusive.toInt()}",
                style = AppTheme.typography.body1
            )
        }
    }
}

Stepped RangeSlider

From preview (Slider.kt:339-351):
var range by remember { mutableStateOf(0.2f..0.6f) }

Column {
    Text("Stepped Range Slider (5 steps)", style = AppTheme.typography.h4)
    RangeSlider(
        value = range,
        onValueChange = { range = it },
        steps = 4,
        modifier = Modifier.fillMaxWidth()
    )
}

SliderColors

The SliderColors class provides color customization (Slider.kt:147-171):
@Composable
fun SliderDefaults.colors(
    thumbColor: Color = AppTheme.colors.primary,
    activeTrackColor: Color = AppTheme.colors.primary,
    activeTickColor: Color = AppTheme.colors.onPrimary,
    inactiveTrackColor: Color = AppTheme.colors.secondary,
    inactiveTickColor: Color = AppTheme.colors.primary,
    disabledThumbColor: Color = AppTheme.colors.disabled,
    disabledActiveTrackColor: Color = AppTheme.colors.disabled,
    disabledActiveTickColor: Color = AppTheme.colors.disabled,
    disabledInactiveTrackColor: Color = AppTheme.colors.disabled,
    disabledInactiveTickColor: Color = Color.Unspecified,
): SliderColors

Advanced Usage

Volume Control

var volume by remember { mutableFloatStateOf(50f) }

Row(
    modifier = Modifier.fillMaxWidth(),
    verticalAlignment = Alignment.CenterVertically,
    horizontalArrangement = Arrangement.spacedBy(16.dp)
) {
    Icon(
        imageVector = if (volume > 0) Icons.Default.VolumeUp else Icons.Default.VolumeOff,
        contentDescription = "Volume"
    )
    Slider(
        value = volume,
        onValueChange = { volume = it },
        valueRange = 0f..100f,
        modifier = Modifier.weight(1f)
    )
    Text("${volume.toInt()}%")
}

Price Range Filter

var priceRange by remember { mutableStateOf(100f..500f) }
val minPrice = 0f
val maxPrice = 1000f

Column {
    Text("Price Range", style = AppTheme.typography.h4)
    
    RangeSlider(
        value = priceRange,
        onValueChange = { priceRange = it },
        valueRange = minPrice..maxPrice,
        steps = 9, // 10 positions: 0, 100, 200, ..., 1000
        modifier = Modifier.fillMaxWidth()
    )
    
    Row(
        modifier = Modifier.fillMaxWidth(),
        horizontalArrangement = Arrangement.SpaceBetween
    ) {
        Text("$${priceRange.start.toInt()}")
        Text("$${priceRange.endInclusive.toInt()}")
    }
}

Brightness Control with Icon

var brightness by remember { mutableFloatStateOf(0.7f) }

Column {
    Text("Brightness", style = AppTheme.typography.h4)
    
    Row(
        verticalAlignment = Alignment.CenterVertically,
        horizontalArrangement = Arrangement.spacedBy(16.dp)
    ) {
        Icon(
            imageVector = Icons.Default.BrightnessLow,
            contentDescription = null,
            tint = AppTheme.colors.textSecondary
        )
        
        Slider(
            value = brightness,
            onValueChange = { brightness = it },
            modifier = Modifier.weight(1f)
        )
        
        Icon(
            imageVector = Icons.Default.BrightnessHigh,
            contentDescription = null,
            tint = AppTheme.colors.primary
        )
    }
}

Styling

Track and Thumb

The slider uses the BasicSlider and BasicRangeSlider components from the com.nomanr.composables.slider package, which handle the visual rendering.

Steps Visualization

When steps > 0, tick marks are displayed on the track:
  • Active ticks: On the filled portion (use activeTickColor)
  • Inactive ticks: On the unfilled portion (use inactiveTickColor)

Accessibility

  • Sliders are keyboard accessible
  • Support for screen readers through semantic properties
  • Clear visual distinction between active and inactive track
  • Disabled state indicated with reduced opacity colors
  • Adequate thumb size for touch interaction

Best Practices

When to Use Steps

  • Use steps when you need discrete values (e.g., rating 1-5)
  • For continuous values like volume or brightness, use steps = 0
  • The number of steps should match your data model

Value Range

  • Always set valueRange to match your expected value range
  • This makes the component more intuitive and avoids conversion logic

Callbacks

  • Use onValueChange for real-time updates during dragging
  • Use onValueChangeFinished for actions after the user releases (e.g., save to database, apply filter)

Source Reference

Full implementation: com.nomanr.lumo.ui.components.Slider.kt

Build docs developers (and LLMs) love