Pantrix UI

Switch Button

A switch: a setting that is on or off and takes effect at once, and the label it is the switch of.

import com.pantrixui.components.elements.switchbutton.PantrixSwitchButton

Examples

Label and supporting text

At its plainest: the state is read from checked, and onCheckedChange reports the value asked for. The track, the label and the supporting text are one target: a press on the words turns the switch too.

Drag the thumb: let go of it past the middle of the track and the switch turns. Let go short of the middle and it settles back, and nothing is reported.

var notifications by rememberSaveable { mutableStateOf(true) }
PantrixSwitchButton(
    checked = notifications,
    onCheckedChange = { notifications = it },
    label = "Notifications",
    supportingText = "Order updates and reminders",
    tagKey = "notifications"
)

Without a label: in a row of the app's own

A row with the words at its start and the switch at its end is a row of the app's own: the words are a PantrixText of their own, and the switch has no label. contentDescription tells a screen reader the switch's name.

In this layout the words are no part of the target: what is pressed is the switch and the touch target kept around it.

var wifi by rememberSaveable { mutableStateOf(false) }
Row(modifier = Modifier.widthIn(max = 360.dp), verticalAlignment = Alignment.CenterVertically) {
    PantrixText(text = "Wi-Fi", modifier = Modifier.weight(1f))
    PantrixSwitchButton(
        checked = wifi,
        onCheckedChange = { wifi = it },
        contentDescription = "Wi-Fi",
        tagKey = "wifi"
    )
}

Error

error turns the supporting text to the theme's danger role, and a screen reader is told the input is invalid. The switch itself keeps its colours: a switch is a setting, and an answer that can be wrong belongs to a checkbox.

Here the example works the error out itself: error is true while the switch is off. Turn the switch on: the text goes back to the colour it had.

var location by rememberSaveable { mutableStateOf(false) }
PantrixSwitchButton(
    checked = location,
    onCheckedChange = { location = it },
    label = "Location",
    supportingText = if (location) "Stores near you are shown" else "Stores cannot be searched while location is off",
    error = !location,
    tagKey = "location"
)

A setting bound to a form

The PantrixSwitchButton that takes a field is bound to a form's field: its value, its error and its name come from the field; checked and onCheckedChange are not given. The field's id is the automation key: the test tag is SwitchButton_two_step.

Press "Save" while two-step sign-in is off: the message of the Rules.mustBeTrue rule takes the place of the supporting text, and the focus goes to the switch. Turn the switch on and try again: the app's snackbar says the settings were saved.

val snackbars = LocalPantrixSnackbars.current
val scope = rememberCoroutineScope()
val form = rememberFormState()
PantrixSwitchButton(
    field = form.field("two_step", initial = false, Rules.mustBeTrue("Your organisation requires this")),
    label = "Two-step sign-in",
    supportingText = "A code is sent by text message at every sign-in"
)
PantrixButton(
    text = "Save",
    onClick = {
        scope.launch { form.submit { snackbars?.show(message = "Settings saved", tagKey = "saved") } }
    },
    form = form,
    tagKey = "save_settings"
)

Disabled and read-only

enabled = false draws the switch in the disabled colours: it takes no press and no drag, and a screen reader is told it is disabled. The thumb's place still shows whether it is on or off.

With onCheckedChange = null the switch is read-only: it takes no press, no drag and no focus, its colours do not change, and it still says its name and its state.

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

Its own colours

colors changes only the fields it is given: here the colours of the track and of the thumb while the switch is on. Turn the switch off: the track, the thumb and the edge are in the theme's colours. In between, the track and the thumb turn from one colour to the other as the thumb slides.

The app's purple is in none of the theme's roles; the colour of the thumb on such a track is the caller's to give as well. A disabled switch is drawn in the theme's colours even when it is given colours.

val own = ownColors()
var batterySaver by rememberSaveable { mutableStateOf(true) }
PantrixSwitchButton(
    checked = batterySaver,
    onCheckedChange = { batterySaver = it },
    label = "Battery saver",
    supportingText = "When on, the track's and the thumb's colours are given from outside; when off, they are the theme's",
    colors = SwitchColors(checkedTrack = own.color, checkedThumb = own.onColor),
    tagKey = "battery_saver"
)

Style

style changes the geometry of one switch. Only the fields that are given change; the rest comes from the theme's and the platform's style.

The first switch is given three fields of the iOS style and is drawn so on every platform: the thumb is always white (thumbAlwaysLight), with a shadow under it (thumbShadow), and the track has no edge when it is off (strokeWidth = 0.dp). The second is a larger track: the track's width and height and the thumb's size in its three states are given together. Its label is set by labelStyle in the theme's callout, in bold; the track stands on that type's first line.

var lightThumb by rememberSaveable { mutableStateOf(true) }
var largeTrack by rememberSaveable { mutableStateOf(false) }
PantrixSwitchButton(
    checked = lightThumb,
    onCheckedChange = { lightThumb = it },
    label = "White thumb, with a shadow and no edge",
    style = SwitchStyle(strokeWidth = 0.dp, thumbAlwaysLight = true, thumbShadow = 2.dp),
    tagKey = "light_thumb"
)
PantrixSwitchButton(
    checked = largeTrack,
    onCheckedChange = { largeTrack = it },
    label = "Large track",
    style = SwitchStyle(
        trackWidth = 64.dp,
        trackHeight = 36.dp,
        thumbSizeOff = 28.dp,
        thumbSizeOn = 28.dp,
        thumbSizePressed = 28.dp
    ),
    tagKey = "large_track",
    labelStyle = PantrixTheme.typography.callout.copy(fontWeight = FontWeight.Bold)
)

The switch at the end

A settings screen: the track at the end of the label (controlPosition = ControlPosition.End). The rows take the column's width (Modifier.fillMaxWidth()), the tracks stand one under another at the column's end, and a long label wraps in the room left.

var wifi by rememberSaveable { mutableStateOf(true) }
var saver by rememberSaveable { mutableStateOf(false) }
Column(Modifier.widthIn(max = 360.dp)) {
    PantrixSwitchButton(
        checked = wifi,
        onCheckedChange = { wifi = it },
        modifier = Modifier.fillMaxWidth(),
        label = "Wi-Fi",
        tagKey = "wifi_end",
        controlPosition = ControlPosition.End
    )
    PantrixSwitchButton(
        checked = saver,
        onCheckedChange = { saver = it },
        modifier = Modifier.fillMaxWidth(),
        label = "Battery saver",
        supportingText = "Cuts back refreshing in the background and some visual effects",
        tagKey = "saver_end",
        controlPosition = ControlPosition.End
    )
}

API

checked

A switch: a setting that is on or off and takes effect at once, and the label it is the switch of.

ParameterTypeDefaultWhat it is
checkedBooleanwhether the switch is on.
onCheckedChange((Boolean) -> Unit)?called with the value the user asks for, the opposite of checked. The switch changes nothing by itself: it is on when checked says so. Null makes it read-only.
modifierModifierModifierthe row's modifier.
enabledBooleantruewhether the switch can be turned. One that cannot is drawn in the disabled colours and says so.
labelString?nullthe text beside the track: part of the target, and the switch'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 supporting text turns to the danger role, and a screen reader is told the input is invalid.
styleSwitchStyle?nullthis call's own style: over every enclosing one and under the density. Only the fields it sets change, on every platform.
colorsSwitchColors?nullthis switch's own colours: the ones it sets, with the theme's for the rest. A disabled switch keeps the theme's.
contentDescriptionString?nullwhat a screen reader says for the switch: its name where it has no label, as in a row of the app's own that has the words elsewhere, or in place of the label.
tagKeyString?nullthe switch's automation key: the row is SwitchButton_<tagKey>. A change is reported as a SwitchButton event that carries the new value, and never its label.
interactionSourceMutableInteractionSource?nullthe switch's interactions, hoisted: its presses, its drags and its focus. It keeps a source of its own when this is null.
labelStyleTextStylePantrixTheme.typography.labelthe type the label is set in: the theme's label unless given. The track stands on its first line at any size. A colour in it is the label's while the switch is enabled; a disabled switch writes it in the disabled colour.
controlPositionControlPositionControlPosition.Startwhere the track stands: before the label, or after it, as a settings screen has it. After it, the track 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 track leaves.

field

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

ParameterTypeDefaultWhat it is
fieldFormField<Boolean>the field the switch shows and changes.
modifierModifierModifierthe row's modifier.
enabledBooleantruewhether the switch can be turned.
labelString?nullthe text beside the track: part of the target, and the switch's name for a screen reader.
supportingTextString?nullhelp under the label, while the field has no error to show there.
styleSwitchStyle?nullthis call's own style: over every enclosing one and under the density. Only the fields it sets change, on every platform.
colorsSwitchColors?nullthis switch's own colours: the ones it sets, with the theme's for the rest. A disabled switch keeps the theme's.
contentDescriptionString?nullwhat a screen reader says for the switch where it has no label, or in place of it.
labelStyleTextStylePantrixTheme.typography.labelthe type the label is set in: the theme's label unless given.
controlPositionControlPositionControlPosition.Startwhere the track stands: before the label, or after it, as a settings screen has it. After it, the track 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 track leaves.

SwitchStyle

PantrixSwitchButton's track and its thumb: how large each is, the track's edge, and what the thumb looks like.

ParameterTypeDefaultWhat it is
trackWidthDpDp.UnspecifiedThe track's width, its edge included.
trackHeightDpDp.UnspecifiedThe track's height, its edge included. The track's ends are as round as it is tall.
thumbSizeOffDpDp.UnspecifiedThe thumb's width and height while the switch is off.
thumbSizeOnDpDp.UnspecifiedThe thumb's width and height while the switch is on.
thumbSizePressedDpDp.UnspecifiedThe thumb's width and height while it is pressed or dragged.
strokeWidthDpDp.UnspecifiedHow thick the edge of a track that is off is. Zero draws none.
thumbAlwaysLightBoolean?nullTrue keeps the thumb white whatever the theme and the state, as iOS has it. False draws it in the border colour when off and in the colour paired with the track's fill when on, as Material has it. Null is unspecified.
thumbShadowDpDp.UnspecifiedHow far the thumb stands above the track: the size of the shadow under it. Zero draws none.

SwitchColors

What PantrixSwitchButton is painted with, for a switch 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.

ParameterTypeDefaultWhat it is
checkedTrackColorColor.UnspecifiedThe track's fill when the switch is on. The theme's primary.
checkedThumbColorColor.UnspecifiedThe thumb when the switch is on: the colour that stands out on checkedTrack. The primary role's onColor, or white where the style keeps the thumb light.
uncheckedTrackColorColor.UnspecifiedThe track's fill when the switch is off. A tint of the theme's borderStrong.
uncheckedThumbColorColor.UnspecifiedThe thumb when the switch is off: the colour that stands out on uncheckedTrack. The theme's borderStrong, or white where the style keeps the thumb light.
borderColorColor.UnspecifiedThe edge of a track that is off, where the style draws one. The theme's borderStrong: a switch that is off is seen by its edge and its thumb.

Accessibility