Checkbox, RadioButton & Switch
None of Compose's three basic selection controls hold any state internally — exactly like
everything else we've built so far, each one is a "dumb" composable that shows a
checked/selected boolean it's given and reports taps through a
callback. This lesson covers all three using RecipeBox screens that need them:
Checkbox for picking multiple ingredients for a shopping list,
RadioButton for choosing one recipe category, and Switch for a single
boolean notification preference.
Checkbox: Multi-Select Ingredients
A shopping-list screen lets the user tick off several of a recipe's ingredients at once. The
natural state shape for "several of these are selected" is a
Set<String> of the checked ingredient names:
var checkedIngredients by remember { mutableStateOf(setOf<String>()) }
Column {
recipe.ingredients.forEach { ingredient ->
Row(verticalAlignment = Alignment.CenterVertically) {
Checkbox(
checked = checkedIngredients.contains(ingredient),
onCheckedChange = { isChecked ->
checkedIngredients = if (isChecked) {
checkedIngredients + ingredient
} else {
checkedIngredients - ingredient
}
}
)
Text(text = ingredient)
}
}
}
Note the +/- operators on the set — because
mutableStateOf only triggers recomposition when the value it holds is reassigned,
we build a brand-new Set each time rather than mutating the existing one in place
(a plain MutableSet mutated via .add() wouldn't trigger recomposition
at all).
RadioButton: Single-Select Category
Choosing exactly one category when editing a recipe — Breakfast, Lunch, Dinner, or Dessert — is
the classic RadioButton case. Because only one option can ever be selected, the
state is a single nullable (or non-null, if a default is set) category value, not a collection:
var selectedCategory by remember { mutableStateOf(RecipeCategory.BREAKFAST) }
Column {
RecipeCategory.values().forEach { category ->
Row(
verticalAlignment = Alignment.CenterVertically,
modifier = Modifier
.fillMaxWidth()
.clickable { selectedCategory = category }
) {
RadioButton(
selected = selectedCategory == category,
onClick = { selectedCategory = category }
)
Text(text = category.label)
}
}
}
Both the Row's clickable and the RadioButton's own
onClick set the same state — this is intentional and standard: it lets the user tap
anywhere on the row, not just the small radio circle itself, to select that category.
Switch: A Single Boolean Toggle
A settings screen's "Notify me about new recipes" preference is a plain boolean with no other
options involved — the simplest possible case, and exactly what Switch is for:
var notificationsEnabled by remember { mutableStateOf(true) }
Row(
verticalAlignment = Alignment.CenterVertically,
horizontalArrangement = Arrangement.SpaceBetween,
modifier = Modifier.fillMaxWidth()
) {
Text("Notify me about new recipes")
Switch(
checked = notificationsEnabled,
onCheckedChange = { notificationsEnabled = it }
)
}
Choosing the Right Control
| Control | Use when |
|---|---|
Checkbox | Zero, one, or many options can be selected independently |
RadioButton | Exactly one option must be selected from a visible list of choices |
Switch | A single, standalone on/off setting with no other related options nearby |
In every case above, state hoisting keeps the pattern identical: the composable receives its
current value and an on...Change/onClick callback, and the caller —
not the control itself — owns the actual mutableStateOf.