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

ControlUse when
CheckboxZero, one, or many options can be selected independently
RadioButtonExactly one option must be selected from a visible list of choices
SwitchA 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.

Note: This is correct, real Compose/Kotlin code — but this website's own project doesn't run a live Android Studio build, so you won't see it actually render on this page.
Next up: Sliders & Chips covers two more selection-style controls: a draggable value for max prep time, and a row of category filter chips.