Button
A button with a text label, the form most buttons take.
import com.pantrixui.components.elements.button.PantrixButton
- Six variants.
variantsays how prominent the button is: the one primary action on a screen isSolid, secondary actions areOutline,SoftandSubtle, tertiary actions areText, and an action that cannot be undone isDestructive. - Five sizes.
sizepicks the height, the padding and the label's text style together: from 24dp to 56dp, in steps of 8dp. The height is a minimum height: when the system's text size is made larger, the button grows with its label. - Icons.
leadingIconstands before the label andtrailingIconafter it, and both take the label's colour. - Loading.
loadingshows a spinner in place of the leading icon and stops presses until the action is done. - Form. A press on a button given a
formvalidates the form first: while a field is invalidonClickis not called, and that field takes the focus. While the form is being submitted, the button shows as loading by itself. - In any role. The default colour is the theme's
primaryrole, and forDestructivethedangerrole;PantrixColor.buttonColors(variant)gives the same variant in another role, andButtonColorsgives it in a colour entirely its own. - Content of its own. There is a second
PantrixButtonthat takes content instead of a label: a counter, two lines, a badge. In that onecontentDescriptionis required.
Examples
Variants
variant picks the button's colour and how prominent it is. A screen has one Solid: the eye goes to it first. Outline, Soft and Subtle are secondary actions; Text is a tertiary action with no surface, like "Cancel". Destructive says the action cannot be undone, and is never the only button of a dialog.
FlowRow(
horizontalArrangement = Arrangement.spacedBy(PantrixTheme.spacing.sm),
verticalArrangement = Arrangement.spacedBy(PantrixTheme.spacing.sm)
) {
ButtonVariant.entries.forEach { variant ->
PantrixButton(text = variant.name, onClick = {}, variant = variant, tagKey = "variant_${variant.name}")
}
}
Sizes
size picks one of five heights: 24, 32, 40, 48 and 56 dp. The default is Medium. The two ends are for particular places: ExtraSmall in a dense table row or beside a field, ExtraLarge for the single action a phone screen is built around.
The height is the button's minimum height. When the system's text size is made larger and the label becomes taller than the button, the button grows with it and the label is not cut.
FlowRow(
horizontalArrangement = Arrangement.spacedBy(PantrixTheme.spacing.sm),
verticalArrangement = Arrangement.spacedBy(PantrixTheme.spacing.sm),
itemVerticalAlignment = Alignment.CenterVertically
) {
ButtonSize.entries.forEach { size ->
PantrixButton(
text = size.name,
onClick = {},
variant = ButtonVariant.Outline,
size = size,
tagKey = "size_${size.name}"
)
}
}
Buttons with icons
leadingIcon stands before the label and trailingIcon after it. The icon is given no colour: it takes the button's content colour by itself. Since it is decoration, its contentDescription is null; a screen reader reads the label alone.
FlowRow(
horizontalArrangement = Arrangement.spacedBy(PantrixTheme.spacing.sm),
verticalArrangement = Arrangement.spacedBy(PantrixTheme.spacing.sm)
) {
PantrixButton(
text = "New folder",
onClick = {},
leadingIcon = { PantrixIcon(imageVector = PantrixIcons.Folder, contentDescription = null) },
tagKey = "new_folder"
)
PantrixButton(
text = "Continue",
onClick = {},
variant = ButtonVariant.Soft,
trailingIcon = { PantrixIcon(imageVector = PantrixIcons.ArrowForward, contentDescription = null) },
tagKey = "continue"
)
PantrixButton(
text = "Refresh",
onClick = {},
variant = ButtonVariant.Outline,
leadingIcon = { PantrixIcon(imageVector = PantrixIcons.Refresh, contentDescription = null) },
tagKey = "refresh"
)
}
Loading and disabled
loading shows a spinner in place of the leading icon and stops presses until the action is done: the same action does not start twice. Press "Save": it shows as loading for two seconds, then the app's snackbar lets you know.
enabled = false draws the button faded, and the button takes no press. The two are not the same thing: a loading button says an action is under way, a disabled one that the action cannot be done right now.
val snackbars = LocalPantrixSnackbars.current
var saving by remember { mutableStateOf(false) }
LaunchedEffect(saving) {
if (saving) {
delay(SAVE_MILLIS)
saving = false
snackbars?.show(message = "Saved")
}
}
FlowRow(
horizontalArrangement = Arrangement.spacedBy(PantrixTheme.spacing.sm),
verticalArrangement = Arrangement.spacedBy(PantrixTheme.spacing.sm)
) {
PantrixButton(text = "Save", onClick = { saving = true }, loading = saving, tagKey = "save")
PantrixButton(text = "Loading", onClick = {}, variant = ButtonVariant.Outline, loading = true, tagKey = "loading_outline")
PantrixButton(text = "Disabled", onClick = {}, enabled = false, tagKey = "disabled")
PantrixButton(text = "Disabled", onClick = {}, variant = ButtonVariant.Outline, enabled = false, tagKey = "disabled_outline")
}
private const val SAVE_MILLIS = 2_000L
Full width
fullWidth stretches the button to the width of the space it is in: the submit button under a form, the two buttons of a sheet. Here the column is 360dp at most, and the buttons fill it.
Column(
modifier = Modifier.widthIn(max = 360.dp),
verticalArrangement = Arrangement.spacedBy(PantrixTheme.spacing.sm)
) {
PantrixButton(text = "Complete order", onClick = {}, size = ButtonSize.Large, fullWidth = true, tagKey = "complete_order")
PantrixButton(text = "Back to shopping", onClick = {}, variant = ButtonVariant.Text, fullWidth = true, tagKey = "back_to_shop")
}
In another role and in a colour of its own
A variant's colours come from the theme's primary role, and Destructive's from the danger role. PantrixColor.buttonColors(variant) gives the same variant in another role: the top row is four variants in the success role.
The colour of the button in the bottom row is in none of the theme's roles: ButtonColors takes the fill, the content's colour and, if wanted, the edge directly.
val success = PantrixTheme.colors.success
val own = ownColors()
FlowRow(
horizontalArrangement = Arrangement.spacedBy(PantrixTheme.spacing.sm),
verticalArrangement = Arrangement.spacedBy(PantrixTheme.spacing.sm)
) {
listOf(ButtonVariant.Solid, ButtonVariant.Soft, ButtonVariant.Outline, ButtonVariant.Text).forEach { variant ->
PantrixButton(
text = "Approve",
onClick = {},
colors = success.buttonColors(variant),
tagKey = "confirm_${variant.name.lowercase()}"
)
}
}
PantrixButton(
text = "Join the campaign",
onClick = {},
colors = ButtonColors(container = own.color, content = own.onColor),
tagKey = "own_colors"
)
Shape and style
shape changes the corners of one button: one of the theme's shapes, or any Shape. style changes the button's geometry: here, with pressedScale, a button that shrinks while it is pressed. Only the fields that are given change; the rest comes from the theme's and the platform's style.
FlowRow(
horizontalArrangement = Arrangement.spacedBy(PantrixTheme.spacing.sm),
verticalArrangement = Arrangement.spacedBy(PantrixTheme.spacing.sm)
) {
PantrixButton(text = "Square", onClick = {}, shape = PantrixTheme.shapes.none, tagKey = "square")
PantrixButton(text = "Slightly rounded", onClick = {}, shape = PantrixTheme.shapes.sm, tagKey = "rounded")
PantrixButton(text = "Fully rounded", onClick = {}, shape = PantrixTheme.shapes.full, tagKey = "pill")
PantrixButton(
text = "Shrinks when pressed",
onClick = {},
variant = ButtonVariant.Soft,
style = ButtonStyle(pressedScale = 0.94f),
tagKey = "press_scale"
)
}
Content of its own
The PantrixButton that takes content instead of a label: the content is a row (RowScope) and is drawn in the button's content colour. Here a counter beside the label goes up with every press.
In this form contentDescription is required: whatever is in the button, a screen reader reads one sentence, "Like, 12 likes".
var likes by rememberSaveable { mutableIntStateOf(12) }
PantrixButton(
onClick = { likes += 1 },
contentDescription = "Like, $likes likes",
variant = ButtonVariant.Soft,
tagKey = "like"
) {
PantrixIcon(imageVector = PantrixIcons.Star, contentDescription = null)
PantrixText(text = "Like")
PantrixText(text = likes.toString(), style = PantrixTheme.typography.footnote)
}
A button that submits a form
A button given a form is the form's submit button. A press validates the form first: while the field is empty onClick is not called, and the field shows its error and takes the focus. If the form is valid, form.submit runs, and while it does the button shows as loading by itself: loading is not given separately.
Leave the field empty and press "Invite", then type a name and try again.
val snackbars = LocalPantrixSnackbars.current
val scope = rememberCoroutineScope()
val form = rememberFormState()
Column(
modifier = Modifier.widthIn(max = 360.dp),
verticalArrangement = Arrangement.spacedBy(PantrixTheme.spacing.md)
) {
PantrixTextField(
field = form.field("name", initial = "", Rules.notBlank("Enter the name of the person to invite")),
modifier = Modifier.fillMaxWidth(),
form = form,
label = "Name"
)
PantrixButton(
text = "Invite",
onClick = {
scope.launch {
form.submit {
delay(SAVE_MILLIS)
snackbars?.show(message = "Invitation sent")
}
}
},
form = form,
tagKey = "invite"
)
}
private const val SAVE_MILLIS = 2_000L
API
text
A button with a text label, the form most buttons take.
| Parameter | Type | Default | What it is |
|---|---|---|---|
text | String | ||
onClick | () -> Unit | ||
modifier | Modifier | Modifier | the button's modifier. |
variant | ButtonVariant | ButtonVariant.Solid | |
size | ButtonSize | ButtonSize.Medium | |
enabled | Boolean | true | whether the button can be pressed: one that cannot is drawn in the disabled colours and does nothing. |
loading | Boolean | false | |
fullWidth | Boolean | false | true stretches the button across the width it is given, its content in the middle. |
leadingIcon | (@Composable () -> Unit)? | null | |
trailingIcon | (@Composable () -> Unit)? | null | what stands after the label: an arrow, or a count. |
style | ButtonStyle? | null | |
shape | Shape | PantrixTheme.style(key = ButtonStyle, override = style).shape.toShape() | the button's corners: by default the resolved style's. Given here, it wins over style's. |
colors | ButtonColors | variant.colors() | this call's own colours, in place of variant's. |
contentDescription | String? | null | |
form | FormState? | null | |
tagKey | String? | null | the button's automation key: the button is Button_<tagKey>, and without one a key of where it is called. Visible text is never the key. |
interactionSource | MutableInteractionSource? | null | the button's interactions, hoisted: its focus, its hover and its presses. It keeps a source of its own when this is null. |
onClick
Slot form, for content a string cannot express.
| Parameter | Type | Default | What it is |
|---|---|---|---|
onClick | () -> Unit | what a press does. Given a form, it is called once the form is valid. | |
contentDescription | String | ||
modifier | Modifier | Modifier | the button's modifier. |
variant | ButtonVariant | ButtonVariant.Solid | the button's prominence, and the colours that come with it. |
size | ButtonSize | ButtonSize.Medium | the button's height, its padding and the type its label is set in. |
enabled | Boolean | true | whether the button can be pressed: one that cannot is drawn in the disabled colours and does nothing. |
loading | Boolean | false | whether the button takes no press while what it began is under way, and tells a screen reader it is loading. What content shows meanwhile is the caller's. |
fullWidth | Boolean | false | true stretches the button across the width it is given, its content in the middle. |
style | ButtonStyle? | null | |
shape | Shape | PantrixTheme.style(ButtonStyle, style).shape.toShape() | the button's corners: by default the resolved style's. Given here, it wins over style's. |
colors | ButtonColors | variant.colors() | this call's own colours, in place of variant's. |
form | FormState? | null | the form the button submits: a press validates it first, and the button takes no press while it is submitting. |
tagKey | String? | null | the button's automation key: the button is Button_<tagKey>, and without one a key of where it is called. Visible text is never the key. |
interactionSource | MutableInteractionSource? | null | the button's interactions, hoisted: its focus, its hover and its presses. It keeps a source of its own when this is null. |
content | @Composable RowScope.() -> Unit |
ButtonStyle
PantrixButton's geometry: its shape, its press feedback, and a height, padding and icon size for each ButtonSize. PantrixIconButton reads the sizes only.
| Parameter | Type | Default | What it is |
|---|---|---|---|
shape | ShapeSpec | ShapeSpec.Unspecified | The button's shape: ShapeRole.Full on Android, desktop and the web, ShapeRole.Medium on iOS. |
pressedScale | Float | Float.NaN | Scale while pressed. 1f means none: the state layer carries the feedback. |
extraSmall | ButtonSizeStyle | ButtonSizeStyle() | ButtonSize.ExtraSmall's geometry. |
small | ButtonSizeStyle | ButtonSizeStyle() | ButtonSize.Small's geometry. |
medium | ButtonSizeStyle | ButtonSizeStyle() | ButtonSize.Medium's geometry. |
large | ButtonSizeStyle | ButtonSizeStyle() | ButtonSize.Large's geometry. |
extraLarge | ButtonSizeStyle | ButtonSizeStyle() | ButtonSize.ExtraLarge's geometry. |
ButtonColors
What a button is painted with. ButtonVariant.colors gives a variant's in primary, and PantrixColor.buttonColors the same prominences in any role.
| Parameter | Type | Default | What it is |
|---|---|---|---|
container | Color | The fill; transparent for an outline or a text button. | |
content | Color | The label's and the icon's colour. | |
border | Color | Color.Unspecified | The edge's colour, or Color.Unspecified for a button without an edge. |
Accessibility
- Accessibility.
contentDescriptionis read in place of the label, not after it: give one only where the label does not say what the button does, as a "+" does not.