Exploring Jetpack Compose: TriStateCheckbox


If you’re enjoying my posts on Jetpack Compose, check out some details on the book I’ll be writing on Compose!

Checkboxes are crucial components when it comes to common areas of our applications. Be it settings screens, forms or any kind of content that needs to allow the user to toggle the checked state of the component – the Checkbox is essential in these scenarios. Alongside the standard checkbox component inside of Jetpack Compose, there is a TriStateCheckbox component that allows us to implement a checkbox component that has three different states, being:

  • on – the checkbox is currently marked as selected
  • off – the checkbox is currently not marked as selected
  • indeterminate – used to visually represent a partial checked state

If we jump into the source code then we’ll see the available composable function for this component:

fun TriStateCheckbox(
    state: ToggleableState,
    onClick: () -> Unit,
    enabled: Boolean = true,
    modifier: Modifier = Modifier.None,
    color: Color = MaterialTheme.colors.secondary

As we can see here there are five available properties that we can pass to the Checkbox function:

  • state – whether or not the checkbox is currently checked. This is required
  • onClick – a callback that will receive change events for when the checkbox selected state changes. This is required
  • enabled – whether or not the component is currently enabled for interaction
  • modifier – the modifier(s) to be applied to the checkbox
  • color – the color to be used for the checkbox. If not provided, then the secondary color from the application theme will be applied

This isn’t actually too different from the Checkbox component that we previously looked at. The main difference here is the use of the ToggleableState reference, instead of a Boolean value to represent the checked state. This ToggleableState is an enum which is used to represent the checked state of the TriStateCheckbox, it can be one of three values:

enum class ToggleableState {

You will also notice that we now also have onClick instead of on CheckChanged, which can no longer be a null value. With these two changes in mind, let’s begin by looking at the behaviour of the state property, starting with an ToggleableState.Indeterminate state:

    state = ToggleableState.Indeterminate,
    onClick = { }

This indeterminate state can be used to represent a condition which is not marked as either on or off. For example, maybe there are groups of nested checkboxes where a parent indeterminate checkbox is marked as on when all of its children checkboxes are selected. The next state we can set for the TriStateCheckbox is ToggleableState.On – this can be used to display the component in its checked state:

    state = ToggleableState.On,
    onClick = { }

Finally we have the ToggleableState.Off state, used to display the component in an unchecked state:

    state = ToggleableState.Off,
    onClick = { }

Now that we’ve seen how these different states look to the user, we need to toggle between them when the component is interacted with. To begin with, statically defining the checked value, along with not reacting to check changed events, doesn’t really resemble how we might handle this in a real world scenario. To approach this, let’s begin by creating a new @Model representation that will house the state for our TriStateCheckbox:

class TriStateFormState(var optionChecked: ToggleableState = ToggleableState.Indeterminate)

This class will only be holding our checked state for this example. But with this in place we can now pass this FormState class to our TriStateCheckboxComponent function, so that it can be used to assign values to the required properties.

fun TriStateCheckboxComponent(formState: TriStateFormState) {
        state = formState.optionChecked,
        onClick = {
            when (formState.optionChecked) {
                ToggleableState.Off -> formState.optionChecked = ToggleableState.Indeterminate
                ToggleableState.On -> formState.optionChecked = ToggleableState.Off
                ToggleableState.Indeterminate -> formState.optionChecked = ToggleableState.On

Here for examples sake we are using the onClick lambda to toggle between the different ToggleableStates. When Off and clicked we’ll set it to Indeterminate, when On and clicked we’ll set it to Off and when Indeterminate we’ll set it to On. This will then be set to the reference inside of our TriStateFromState model, which will cause the current state of our component to be reflected via the use of the state property. The logic here will likely depend on the use case for the component.

Similar to how we can control the color of our component for the checkbox, the behaviour is the same when it comes to the TriStateCheckbox:

    color = Color.Red

Alongside the above similarities and differences, if we dive into the source code of the primary Checkbox component we can see a huge dependence on the TriStateCheckbox:

fun Checkbox(
    checked: ...
) {
        state = ToggleableState(checked),
        onClick = { onCheckedChange(!checked) },
        enabled = enabled,
        color = color,
        modifier = modifier

Under the hood, the checkbox actually just backs off of the TriStateCheckbox component, reusing all of the code used to construct the component. We can see here that the Checkbox utilises its own properties to satisfy the requirements of the TriStateCheckbox:

  • state – for the state, a new instance of the ToggleableState is instantiated. The ToggleableState class has a helper function that returns either On or Off based on the provided value. This allows the Checkbox to map its state to the required ToggleableState used for the TriStateCheckbox
fun ToggleableState(value: Boolean) = if (value) On else Off
  • onClick – the checkbox uses a onCheckedChange lambda which will receive a boolean value whenever the checkbox is toggled on or off. Here, the Checkbox uses its checked value to trigger its own onCheckChange lambda whenever onClick is triggered. That way whenever the TriStateCheckbox is clicked, the checked value will be flipped through the change callback

In this post we’ve taken a quick dive into the TriStateCheckbox component from Jetpack Compose. Stay tuned for the next post and in the meantime, subscribe to updates on my Jetpack Compose book 🙂

About the author


Add Comment