Chip
A chip: a compact action, or an item the user put in and can take out again, such as a recipient, a tag or an applied filter.
import com.pantrixui.components.elements.chip.PantrixChip
- Action. A chip given
onClickis a button, and its name is its label.leadingIconputs an icon before the label. - Filter. A filter that is on is drawn filled and carries a tick in the icon's place: it is not colour alone that says it is on. A screen reader hears a checkbox that is selected or not. The chip does not change by itself: it is whatever
selectedsays. - Remove button.
onRemoveputs a cross at the chip's end: it is a target of its own, and a screen reader reads it with the label, "Remove Ayşe Yılmaz". The chip does not take itself out;onRemovedoes. - A chip that only shows a value. A chip given
onClick = nullshows a value, and a filter givenonSelectedChange = nullis read-only: they take no press and no focus, but still tell a screen reader their names. - Long label. A label too long for its room ends in an ellipsis; the icon and the cross stay whole.
- Disabled. With
enabled = falseneither the chip nor its cross can be pressed; the chip is drawn in the theme's disabled colours. - Colours and style.
colorschanges only the colours it gives, andstyleonly the dimensions it gives. - Platform. On Android it is 32dp tall with medium rounded corners; on iOS it is a pill of 32dp, and on desktop and the web one of 30dp. The platform's touch target is reserved around it.
- Automation. A chip given a
tagKeycarries the tagChip_<tagKey>, and its crossChipRemove_<tagKey>. A press is reported as aChipevent, and the event never carries the label's text.
Examples
An action chip
The first form: a chip that takes onClick is a button that does something when it is pressed, and its name is its label. Its shortest form is the first chip: a label and an onClick. In the second, leadingIcon stands before the label. The third is disabled with enabled = false: it is drawn faded and takes no press.
Press a chip: the line below says which one was pressed last.
var last by rememberSaveable { mutableStateOf<String?>(null) }
FlowRow(horizontalArrangement = Arrangement.spacedBy(PantrixTheme.spacing.sm)) {
PantrixChip(label = "Share", onClick = { last = "Share" }, tagKey = "share")
PantrixChip(
label = "Add to calendar",
onClick = { last = "Add to calendar" },
leadingIcon = PantrixIcons.Calendar,
tagKey = "calendar"
)
PantrixChip(label = "Archive", onClick = { last = "Archive" }, enabled = false, tagKey = "archive")
}
PantrixText(
text = last?.let { "Last action: $it" } ?: "No action pressed yet",
style = PantrixTheme.typography.footnote,
color = PantrixTheme.colors.textSecondary,
tagKey = "last_action"
)
A selectable chip: filters
The second form: a chip that takes selected and onSelectedChange is a filter that goes on and off. The chip changes nothing by itself: onSelectedChange reports the value the user asks for, and here the screen's state keeps the keys of the filters that are on.
A filter that is on is drawn filled and carries a tick at its start; it is not colour alone that shows it is on. In the filter that has an icon, "Favourites", the tick takes the icon's place and the chip's width does not change. The filters are in a row that scrolls sideways; the line below says how many are on.
var on by rememberSaveable { mutableStateOf(listOf("stock")) }
Row(
modifier = Modifier.horizontalScroll(rememberScrollState()),
horizontalArrangement = Arrangement.spacedBy(PantrixTheme.spacing.sm)
) {
FILTERS.forEach { filter ->
PantrixChip(
label = filter.label,
selected = filter.key in on,
onSelectedChange = { selected -> on = if (selected) on + filter.key else on - filter.key },
leadingIcon = filter.icon,
tagKey = "filter_${filter.key}"
)
}
}
PantrixText(
text = if (on.isEmpty()) "No filters" else "Filters on: ${on.size}",
style = PantrixTheme.typography.footnote,
color = PantrixTheme.colors.textSecondary,
tagKey = "filters_on"
)
private class ChipFilter(
val key: String,
val label: String,
val icon: ImageVector? = null
)
private val FILTERS = listOf(
ChipFilter("stock", "In stock"),
ChipFilter("sale", "On sale"),
ChipFilter("starred", "Favourites", PantrixIcons.Star),
ChipFilter("shipping", "Free shipping"),
ChipFilter("new", "New arrivals"),
ChipFilter("pickup", "Store pickup")
)
Removable chips: recipients
onRemove puts a cross at the chip's end. The cross is a button in its own right: a screen reader says "Remove Ayşe Yılmaz", so the crosses of a row can be told apart. The chip does not take itself out; it is the code here that takes the recipient out of the list.
A press on the chip itself opens the recipient; a press on the cross does not. Recipients that do not fit in the row go on to the next one. "Restore" puts them all back.
var kept by rememberSaveable { mutableStateOf(PEOPLE.map { it.key }) }
var opened by rememberSaveable { mutableStateOf<String?>(null) }
FlowRow(horizontalArrangement = Arrangement.spacedBy(PantrixTheme.spacing.sm)) {
PEOPLE.filter { it.key in kept }.forEach { person ->
PantrixChip(
label = person.name,
onClick = { opened = person.name },
leadingIcon = PantrixIcons.Person,
onRemove = {
kept = kept - person.key
if (opened == person.name) opened = null
},
tagKey = "recipient_${person.key}"
)
}
}
Row(
horizontalArrangement = Arrangement.spacedBy(PantrixTheme.spacing.sm),
verticalAlignment = Alignment.CenterVertically
) {
PantrixText(
text = opened?.let { "Opened: $it" } ?: "Recipients: ${kept.size}",
style = PantrixTheme.typography.footnote,
color = PantrixTheme.colors.textSecondary,
tagKey = "recipients"
)
PantrixButton(
text = "Restore",
onClick = { kept = PEOPLE.map { it.key } },
variant = ButtonVariant.Text,
size = ButtonSize.Small,
enabled = kept.size < PEOPLE.size,
tagKey = "restore"
)
}
private class ChipRecipient(
val key: String,
val name: String
)
private val PEOPLE = listOf(
ChipRecipient("ayse", "Ayşe Yılmaz"),
ChipRecipient("ibrahim", "İbrahim Öztürk"),
ChipRecipient("ilker", "İlker Kaya"),
ChipRecipient("selin", "Selin Çağlar")
)
Chips that only show a value
Two chips that cannot be pressed. "Members only" is a filter and was given onSelectedChange = null: it is read-only and takes no press and no focus, but still tells a screen reader its name and that it is on. With onClick = null, "Draft" only shows a value: it is one item for a screen reader.
Neither of them is disabled: they are drawn in their own colours, and simply have nothing to press.
Row(horizontalArrangement = Arrangement.spacedBy(PantrixTheme.spacing.sm)) {
PantrixChip(label = "Members only", selected = true, onSelectedChange = null, tagKey = "locked")
PantrixChip(label = "Draft", onClick = null, tagKey = "draft")
}
A long label and a wide chip
A label too long for its room ends in an ellipsis. The icon and the cross take their room first and the label is given what is left: a long label gets shorter, while its icon and its cross stay whole. The first chip cannot be pressed but can be removed: a chip given onClick = null still carries its cross when onRemove is given.
The second chip is wider than it needs to be, 220dp: its icon and its label are in the middle, as in a button, and its cross is at the end.
PantrixChip(
label = "Third-quarter reports as they were sent to the board, with all their attachments",
onClick = null,
leadingIcon = PantrixIcons.Folder,
onRemove = {},
tagKey = "long"
)
PantrixChip(
label = "Kotlin",
onClick = null,
modifier = Modifier.width(220.dp),
leadingIcon = PantrixIcons.Star,
onRemove = {},
tagKey = "wide"
)
Disabled chips
With enabled = false the chip cannot be pressed, and it says so to a screen reader too. A disabled chip is drawn in the theme's disabled colours: a filter that is on takes the disabled fill; a chip without a fill stays without one, and its label fades. Along with the chip, its cross cannot be pressed either.
The last chip was given colors, but it was drawn like the others all the same: so that a disabled chip looks the same everywhere in a product, it uses the theme's colours and not its own.
val own = ownColors()
FlowRow(
horizontalArrangement = Arrangement.spacedBy(PantrixTheme.spacing.sm),
verticalArrangement = Arrangement.spacedBy(PantrixTheme.spacing.sm)
) {
PantrixChip(label = "In stock", selected = true, onSelectedChange = {}, enabled = false, tagKey = "disabled_on")
PantrixChip(label = "On sale", selected = false, onSelectedChange = {}, enabled = false, tagKey = "disabled_off")
PantrixChip(
label = "Selin Çağlar",
onClick = {},
leadingIcon = PantrixIcons.Person,
onRemove = {},
enabled = false,
tagKey = "disabled_recipient"
)
PantrixChip(
label = "My collection",
selected = true,
onSelectedChange = {},
enabled = false,
colors = ChipColors(selectedContainer = own.color, selectedContent = own.onColor),
tagKey = "disabled_own_colors"
)
}
Its own colours
Two chips whose colours were given by the caller. The first is a filled action: its fill, its label and its icon were given, and its edge was removed with Color.Transparent. The second is a filter, and only the colours it is drawn in while it is on were given: while it is off it is drawn in the theme's. Press the filter to turn it off and see the difference.
ChipColors changes only the colours that are given; one that is left out is decided by the theme's role.
val own = ownColors()
var inCollection by rememberSaveable { mutableStateOf(true) }
Row(horizontalArrangement = Arrangement.spacedBy(PantrixTheme.spacing.sm)) {
PantrixChip(
label = "New season",
onClick = {},
leadingIcon = PantrixIcons.Star,
colors = ChipColors(
container = own.container,
content = own.onContainer,
icon = own.onContainer,
border = Color.Transparent
),
tagKey = "new_season"
)
PantrixChip(
label = "My collection",
selected = inCollection,
onSelectedChange = { inCollection = it },
colors = ChipColors(selectedContainer = own.color, selectedContent = own.onColor),
tagKey = "my_collection"
)
}
Shape and style
style changes the geometry of this chip alone; the fields that are not given come from the theme's and the platform's style. The first chip has no style and its shape is the platform's: medium rounded on Android, fully rounded on the others. The second's corners are slightly rounded with ShapeRole.Small, and the third's are fully rounded with ShapeRole.Full. The last is 40dp tall and has 20dp of room at each side of its label.
FlowRow(
horizontalArrangement = Arrangement.spacedBy(PantrixTheme.spacing.sm),
verticalArrangement = Arrangement.spacedBy(PantrixTheme.spacing.sm),
itemVerticalAlignment = Alignment.CenterVertically
) {
PantrixChip(label = "No style", onClick = {}, tagKey = "plain")
PantrixChip(label = "Slightly rounded", onClick = {}, style = ChipStyle(shape = ShapeRole.Small), tagKey = "rounded")
PantrixChip(label = "Fully rounded", onClick = {}, style = ChipStyle(shape = ShapeRole.Full), tagKey = "pill")
PantrixChip(
label = "Tall",
onClick = {},
style = ChipStyle(height = 40.dp, horizontalPadding = 20.dp),
tagKey = "tall"
)
}
API
label
A chip: a compact action, or an item the user put in and can take out again, such as a recipient, a tag or an applied filter.
| Parameter | Type | Default | What it is |
|---|---|---|---|
label | String | the chip's text, and its name for a screen reader. | |
onClick | (() -> Unit)? | called when the chip is pressed. Null makes a chip that only shows a value. | |
modifier | Modifier | Modifier | the chip's modifier, outside the touch target it reserves. |
leadingIcon | ImageVector? | null | an icon before the label, in the style's icon size. |
onRemove | (() -> Unit)? | null | called when the remove button is pressed. Null leaves the button out. |
enabled | Boolean | true | whether the chip and its remove button can be pressed. A chip that cannot is drawn in the disabled colours and says so. |
style | ChipStyle? | null | this call's own style: over every enclosing one and under the density. Only the fields it sets change, on every platform. |
colors | ChipColors? | null | this chip's own colours: the ones it sets, with the theme's for the rest. A disabled chip keeps the theme's. |
tagKey | String? | null | the chip's automation key: the chip is Chip_<tagKey> and its remove button ChipRemove_<tagKey>. A press is reported as a Chip event, click or remove, which never carries the label. |
interactionSource | MutableInteractionSource? | null | the chip's interactions, hoisted. It keeps a source of its own when this is null, and the remove button always has its own. |
label
A filter: a chip that is on or off.
| Parameter | Type | Default | What it is |
|---|---|---|---|
label | String | the chip's text, and its name for a screen reader. | |
selected | Boolean | whether the filter is on. | |
onSelectedChange | ((Boolean) -> Unit)? | called with the value the user asks for, the opposite of selected. The chip changes nothing by itself: it is on when selected says so. Null makes it read-only. | |
modifier | Modifier | Modifier | the chip's modifier, outside the touch target it reserves. |
leadingIcon | ImageVector? | null | an icon before the label, in the style's icon size. A selected chip shows a tick in its place. |
enabled | Boolean | true | whether the chip can be pressed. A chip that cannot is drawn in the disabled colours and says so. |
style | ChipStyle? | null | this call's own style: over every enclosing one and under the density. Only the fields it sets change, on every platform. |
colors | ChipColors? | null | this chip's own colours: the ones it sets, with the theme's for the rest. A disabled chip keeps the theme's. |
tagKey | String? | null | the chip's automation key: the chip is Chip_<tagKey>. A change is reported as a Chip event, toggle, which carries the value asked for and never the label. |
interactionSource | MutableInteractionSource? | null | the chip's interactions, hoisted. It keeps a source of its own when this is null. |
ChipStyle
PantrixChip's geometry.
| Parameter | Type | Default | What it is |
|---|---|---|---|
shape | ShapeSpec | ShapeSpec.Unspecified | The chip's shape: a step of the brand's shape scale, or a fixed shape. |
height | Dp | Dp.Unspecified | The chip's height. A label set larger than it has room for, at a large font scale, makes the chip taller. |
horizontalPadding | Dp | Dp.Unspecified | The room between the label and the chip's edge, on a side that has no icon. |
iconPadding | Dp | Dp.Unspecified | The room between an icon and the chip's edge, and between an icon and the label. |
iconSize | Dp | Dp.Unspecified | The width and height of the leading icon, of a selected chip's tick and of the remove button's cross. |
borderWidth | Dp | Dp.Unspecified | The width of the edge of a chip that is not selected. The edge is drawn inside the chip, which is as large with it as without. |
ChipColors
What PantrixChip is painted with, for a chip 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:
| Parameter | Type | Default | What it is |
|---|---|---|---|
container | Color | Color.Unspecified | The chip's fill while it is not selected. None by default: the chip is its edge and its label. |
content | Color | Color.Unspecified | The label while the chip is not selected. The theme's textPrimary. |
icon | Color | Color.Unspecified | The icon before the label and the cross of the remove button, while the chip is not selected. The theme's textSecondary. |
border | Color | Color.Unspecified | The chip's edge while it is not selected. The theme's border. |
selectedContainer | Color | Color.Unspecified | The fill of a filter that is on. The primary role's container. |
selectedContent | Color | Color.Unspecified | The label and the tick of a filter that is on: the colour that reads on selectedContainer. The primary role's onContainer. |