Sliders & Chips
Filtering the recipe list by a continuous value, like prep time, doesn't fit checkboxes or radio buttons well — a Slider lets the user drag to a value along a range instead of picking from discrete rows. Filtering by category, on the other hand, works nicely as a horizontal row of small, tappable pills — Material 3's chips. This lesson adds both to the bottom sheet built two lessons ago.
Slider: A Continuous Value
Slider reports a plain Float through onValueChange, so a
"max prep time" filter needs a small conversion between Float and the
Int minutes RecipeBox actually stores:
var maxPrepMinutes by remember { mutableStateOf(60f) }
Column {
Text("Max prep time: ${maxPrepMinutes.toInt()} min")
Slider(
value = maxPrepMinutes,
onValueChange = { maxPrepMinutes = it },
valueRange = 5f..120f,
steps = 22 // snaps to 5-minute increments across the range
)
}
steps counts the number of discrete stops between the two ends of
valueRange, not including the endpoints themselves — here, 22 intermediate stops
across a 115-minute range works out to roughly 5-minute increments. Leaving steps
unset (or 0) makes the slider fully continuous instead.
FilterChip: Toggleable Category Filters
A FilterChip is a small, pill-shaped toggle — visually distinct when
selected — perfect for "show only these categories" style filtering, where more
than one chip can be active simultaneously:
var selectedCategories by remember { mutableStateOf(setOf<RecipeCategory>()) }
Row(horizontalArrangement = Arrangement.spacedBy(8.dp)) {
RecipeCategory.values().forEach { category ->
val isSelected = selectedCategories.contains(category)
FilterChip(
selected = isSelected,
onClick = {
selectedCategories = if (isSelected) {
selectedCategories - category
} else {
selectedCategories + category
}
},
label = { Text(category.label) },
leadingIcon = if (isSelected) {
{ Icon(Icons.Filled.Check, contentDescription = null, modifier = Modifier.size(18.dp)) }
} else null
)
}
}
This renders exactly four chips — Breakfast, Lunch, Dinner, Dessert — each independently
toggleable, and leadingIcon conditionally shows a small checkmark only on the
chips currently selected.
AssistChip: A Suggested, One-Shot Action
Where FilterChip holds a persistent selected/unselected state, an
AssistChip is stateless — it's a compact button for a single suggested action, like
offering to clear every active filter at once:
AssistChip(
onClick = {
selectedCategories = emptySet()
maxPrepMinutes = 120f
},
label = { Text("Clear filters") },
leadingIcon = {
Icon(Icons.Filled.Clear, contentDescription = null, modifier = Modifier.size(18.dp))
}
)
FilterChip vs. AssistChip vs. RadioButton
It's worth being deliberate here: a row of FilterChips and a column of
RadioButtons can look similar in a mockup, but they mean different things —
FilterChip naturally supports multiple simultaneous selections (any combination of
categories), while RadioButton, as covered last lesson, enforces exactly one. If
RecipeBox's filter sheet only ever allowed a single category at a time, radio buttons would
actually be the more honest control despite chips being visually trendier.
RoundedCornerShape, CircleShape, and fully custom polygons — including a category badge that reuses the same category data these chips filter by.