Pantrix UI

Checkbox

A checkbox: a box that is checked or not, and the label it is the box of.

import com.pantrixui.components.elements.checkbox.PantrixCheckbox

Examples

Label and supporting text

Its plainest form: checked reads the state, and onCheckedChange reports the value that is asked for. label stands beside the box and supportingText under it, and the three are one target. Press the text: the box still changes.

var newsletter by rememberSaveable { mutableStateOf(true) }
PantrixCheckbox(
    checked = newsletter,
    onCheckedChange = { newsletter = it },
    label = "Weekly newsletter",
    supportingText = "One email a week; you can unsubscribe at any time",
    tagKey = "newsletter"
)

Three states: select all

PantrixTriStateCheckbox draws a third state. While some of the boxes under it are selected, "All notifications" is neither checked nor unchecked: it shows a dash, and that is how a screen reader reads it.

The caller works out the state, not the box: On if all are selected, Off if none is selected, Indeterminate in between. onClick only reports that it was pressed; here it selects them all, and when all are selected it clears them all.

val notifications = listOf("Shipping status", "Promotions", "Account security")
var chosen by remember { mutableStateOf(setOf(notifications.first())) }
val all = when (chosen.size) {
    0 -> ToggleableState.Off
    notifications.size -> ToggleableState.On
    else -> ToggleableState.Indeterminate
}
Column(verticalArrangement = Arrangement.spacedBy(PantrixTheme.spacing.xs)) {
    PantrixTriStateCheckbox(
        state = all,
        onClick = { chosen = if (all == ToggleableState.On) emptySet() else notifications.toSet() },
        label = "All notifications",
        tagKey = "all_notifications"
    )
    Column(modifier = Modifier.padding(start = PantrixTheme.spacing.xl)) {
        notifications.forEachIndexed { index, name ->
            PantrixCheckbox(
                checked = name in chosen,
                onCheckedChange = { on -> chosen = if (on) chosen + name else chosen - name },
                label = name,
                tagKey = "notification_$index"
            )
        }
    }
}

Error

error turns the box and the supporting text to the theme's danger role, and a screen reader is told the input is invalid. While the error lasts, the supporting text is the message itself: it says what to put right.

Here the example works out the error itself: error is true while the box is unchecked. Check the box: the colour and the text go back to what they were.

var read by rememberSaveable { mutableStateOf(false) }
PantrixCheckbox(
    checked = read,
    onCheckedChange = { read = it },
    label = "I have read the privacy notice",
    supportingText = if (read) "Thank you" else "Check this box to continue",
    error = !read,
    tagKey = "privacy"
)

Consent bound to a form

The PantrixCheckbox that takes a field is bound to a field of a form: it gets its value, its error and its name from the field; checked and onCheckedChange are not given. The field's id is the automation key: this box is Checkbox_terms.

Press "Send" without checking the box: the message of the Rules.mustBeTrue rule takes the place of the supporting text, and the focus goes to the box. When the box is checked the message goes away; when the form is submitted, the app's snackbar lets you know.

val snackbars = LocalPantrixSnackbars.current
val scope = rememberCoroutineScope()
val form = rememberFormState()
PantrixCheckbox(
    field = form.field("terms", initial = false, Rules.mustBeTrue("Accept the terms to continue")),
    label = "I accept the terms of use",
    supportingText = "You can read them before sending"
)
PantrixButton(
    text = "Send",
    onClick = {
        scope.launch { form.submit { snackbars?.show(message = "Form sent", tagKey = "form_sent") } }
    },
    form = form,
    tagKey = "send"
)

Disabled and read-only

enabled = false draws the box in the disabled colours: the box takes no press, and a screen reader is told it is disabled.

With onCheckedChange = null the box is read-only: it takes no press and no focus, its colours do not change, and it still says its name and its state. It is only for showing what was selected.

PantrixCheckbox(checked = true, onCheckedChange = {}, enabled = false, label = "Disabled", tagKey = "off")
PantrixCheckbox(
    checked = true,
    onCheckedChange = null,
    label = "Read-only",
    supportingText = "Cannot be pressed; it only shows its state",
    tagKey = "read_only"
)

Without a label: in a row of a list

A box without a label: in a row of a table or a list, the text is already in the row. contentDescription tells a screen reader the box's name: "Select order 1042".

The text in the row is no part of the box: what is pressed is the box and the touch target reserved around it.

var selected by rememberSaveable { mutableStateOf(setOf("1042")) }
Column(modifier = Modifier.widthIn(max = 360.dp)) {
    listOf("1042", "1043", "1044").forEach { number ->
        Row(
            horizontalArrangement = Arrangement.spacedBy(PantrixTheme.spacing.sm),
            verticalAlignment = Alignment.CenterVertically
        ) {
            PantrixCheckbox(
                checked = number in selected,
                onCheckedChange = { on -> selected = if (on) selected + number else selected - number },
                contentDescription = "Select order $number",
                tagKey = "order_$number"
            )
            PantrixText(text = "Order $number", modifier = Modifier.weight(1f))
            PantrixText(text = "Preparing", color = PantrixTheme.colors.textSecondary)
        }
    }
}
PantrixText(text = "Orders selected: ${selected.size}", color = PantrixTheme.colors.textSecondary)

Its own colours

colors changes only the fields that are given: the fill of the "Gift wrap" box is in the app's own purple, and its tick in the colour that reads on that purple; its edge stays in the theme's colour. Uncheck the box: its edge is the same as the other boxes'.

These are the colours of a box that can be pressed and is not in error. The second box was given the same colours too, but it is disabled: it is drawn in the theme's disabled colours. A box in error stays in the theme's danger role as well.

val own = ownColors()
var giftWrap by rememberSaveable { mutableStateOf(true) }
PantrixCheckbox(
    checked = giftWrap,
    onCheckedChange = { giftWrap = it },
    label = "Gift wrap",
    supportingText = "The box's fill and tick were given by the caller",
    colors = CheckboxColors(checked = own.color, checkmark = own.onColor),
    tagKey = "gift_wrap"
)
PantrixCheckbox(
    checked = true,
    onCheckedChange = {},
    enabled = false,
    label = "Gift note",
    supportingText = "The same colours were given; a disabled box stays in the theme's colour",
    colors = CheckboxColors(checked = own.color, checkmark = own.onColor),
    tagKey = "gift_note"
)

Style

style changes the geometry of one box: size is the box's size, shape its corners, and strokeWidth the thickness of the lines of the edge and the tick. Only the fields that are given change; the rest comes from the theme's and the platform's style.

The first box is 24dp and its line is 3dp; the second has square corners. The box's colour is no part of the style: it comes from the state and from colors.

var large by rememberSaveable { mutableStateOf(true) }
var square by rememberSaveable { mutableStateOf(true) }
PantrixCheckbox(
    checked = large,
    onCheckedChange = { large = it },
    label = "Large box, thick line",
    style = CheckboxStyle(size = 24.dp, strokeWidth = 3.dp),
    tagKey = "large"
)
PantrixCheckbox(
    checked = square,
    onCheckedChange = { square = it },
    label = "Square box",
    style = CheckboxStyle(shape = ShapeSpec.Fixed(RectangleShape)),
    tagKey = "square"
)

The box at the end

The box at the end of the label (controlPosition = ControlPosition.End): a list's options, the boxes one under another at the column's end. The rows take the column's width (Modifier.fillMaxWidth()).

var photos by rememberSaveable { mutableStateOf(true) }
var contacts by rememberSaveable { mutableStateOf(false) }
Column(Modifier.widthIn(max = 360.dp)) {
    PantrixCheckbox(
        checked = photos,
        onCheckedChange = { photos = it },
        modifier = Modifier.fillMaxWidth(),
        label = "Back up photos",
        tagKey = "photos_end",
        controlPosition = ControlPosition.End
    )
    PantrixCheckbox(
        checked = contacts,
        onCheckedChange = { contacts = it },
        modifier = Modifier.fillMaxWidth(),
        label = "Back up contacts",
        tagKey = "contacts_end",
        controlPosition = ControlPosition.End
    )
}

API

checked

A checkbox: a box that is checked or not, and the label it is the box of.

ParameterTypeDefaultWhat it is
checkedBooleanwhether the box is checked.
onCheckedChange((Boolean) -> Unit)?called with the value the user asks for, the opposite of checked. The checkbox changes nothing by itself: it is checked when checked says so. Null makes it read-only.
modifierModifierModifierthe row's modifier.
enabledBooleantruewhether the checkbox can be toggled. One that cannot is drawn in the disabled colours and says so.
labelString?nullthe text beside the box: part of the target, and the checkbox's name for a screen reader.
supportingTextString?nullhelp under the label, in a smaller type: the message, while error is true.
errorBooleanfalsewhether the value is wrong: the box and the supporting text turn to the danger role, and a screen reader is told the input is invalid.
styleCheckboxStyle?nullthis call's own style: over every enclosing one and under the density. Only the fields it sets change, on every platform.
colorsCheckboxColors?nullthis checkbox's own colours: the ones it sets, with the theme's for the rest. A disabled box and one in error keep the theme's.
contentDescriptionString?nullwhat a screen reader says for the checkbox: its name where it has no label, as in a table's row, or in place of the label.
tagKeyString?nullthe checkbox's automation key: the row is Checkbox_<tagKey>. A toggle is reported as a Checkbox event that carries the state the box had, and never its label.
interactionSourceMutableInteractionSource?nullthe checkbox's interactions, hoisted. It keeps a source of its own when this is null.
controlPositionControlPositionControlPosition.Startwhere the box stands: before the label, as a list of choices has it, or after it. After it, the box is at the end of the width the row is given, Modifier.fillMaxWidth() lining it up with the others, or right after the label where the row is given none; the label wraps in what the box leaves.

field

A checkbox bound to a form's field: its value, its error and its name come from field.

ParameterTypeDefaultWhat it is
fieldFormField<Boolean>the field the checkbox shows and changes.
modifierModifierModifierthe row's modifier.
enabledBooleantruewhether the checkbox can be toggled.
labelString?nullthe text beside the box: part of the target, and the checkbox's name for a screen reader.
supportingTextString?nullhelp under the label, while the field has no error to show there.
styleCheckboxStyle?nullthis call's own style: over every enclosing one and under the density. Only the fields it sets change, on every platform.
colorsCheckboxColors?nullthis checkbox's own colours: the ones it sets, with the theme's for the rest. A disabled box and one in error keep the theme's.
contentDescriptionString?nullwhat a screen reader says for the checkbox where it has no label, or in place of it.
controlPositionControlPositionControlPosition.Startwhere the box stands: before the label, as a list of choices has it, or after it. After it, the box is at the end of the width the row is given, Modifier.fillMaxWidth() lining it up with the others, or right after the label where the row is given none; the label wraps in what the box leaves.

CheckboxStyle

PantrixCheckbox's box: its size, its corners and its edge.

ParameterTypeDefaultWhat it is
sizeDpDp.UnspecifiedThe box's width and height, its edge included.
shapeShapeSpecShapeSpec.UnspecifiedThe box's corners: a step of the brand's shape scale, so a brand with square corners has square boxes.
strokeWidthDpDp.UnspecifiedHow thick the box's edge is, and the tick's and the dash's line.

CheckboxColors

What PantrixCheckbox is painted with, for a checkbox that does not take the theme's: a colour that is set is used, and one that is left out stays the theme's role. A product gives the colours it changes and no others:

ParameterTypeDefaultWhat it is
checkedColorColor.UnspecifiedThe box's fill when it is checked or indeterminate. The theme's primary.
checkmarkColorColor.UnspecifiedThe tick and the dash on the fill: the colour that reads on checked. The primary role's onColor.
borderColorColor.UnspecifiedThe box's edge when it is not checked. The theme's borderStrong: an empty box is nothing but its edge.

Accessibility