Pantrix UI

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

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.

ParameterTypeDefaultWhat it is
labelStringthe 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.
modifierModifierModifierthe chip's modifier, outside the touch target it reserves.
leadingIconImageVector?nullan icon before the label, in the style's icon size.
onRemove(() -> Unit)?nullcalled when the remove button is pressed. Null leaves the button out.
enabledBooleantruewhether the chip and its remove button can be pressed. A chip that cannot is drawn in the disabled colours and says so.
styleChipStyle?nullthis call's own style: over every enclosing one and under the density. Only the fields it sets change, on every platform.
colorsChipColors?nullthis chip's own colours: the ones it sets, with the theme's for the rest. A disabled chip keeps the theme's.
tagKeyString?nullthe 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.
interactionSourceMutableInteractionSource?nullthe 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.

ParameterTypeDefaultWhat it is
labelStringthe chip's text, and its name for a screen reader.
selectedBooleanwhether 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.
modifierModifierModifierthe chip's modifier, outside the touch target it reserves.
leadingIconImageVector?nullan icon before the label, in the style's icon size. A selected chip shows a tick in its place.
enabledBooleantruewhether the chip can be pressed. A chip that cannot is drawn in the disabled colours and says so.
styleChipStyle?nullthis call's own style: over every enclosing one and under the density. Only the fields it sets change, on every platform.
colorsChipColors?nullthis chip's own colours: the ones it sets, with the theme's for the rest. A disabled chip keeps the theme's.
tagKeyString?nullthe 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.
interactionSourceMutableInteractionSource?nullthe chip's interactions, hoisted. It keeps a source of its own when this is null.

ChipStyle

PantrixChip's geometry.

ParameterTypeDefaultWhat it is
shapeShapeSpecShapeSpec.UnspecifiedThe chip's shape: a step of the brand's shape scale, or a fixed shape.
heightDpDp.UnspecifiedThe chip's height. A label set larger than it has room for, at a large font scale, makes the chip taller.
horizontalPaddingDpDp.UnspecifiedThe room between the label and the chip's edge, on a side that has no icon.
iconPaddingDpDp.UnspecifiedThe room between an icon and the chip's edge, and between an icon and the label.
iconSizeDpDp.UnspecifiedThe width and height of the leading icon, of a selected chip's tick and of the remove button's cross.
borderWidthDpDp.UnspecifiedThe 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:

ParameterTypeDefaultWhat it is
containerColorColor.UnspecifiedThe chip's fill while it is not selected. None by default: the chip is its edge and its label.
contentColorColor.UnspecifiedThe label while the chip is not selected. The theme's textPrimary.
iconColorColor.UnspecifiedThe icon before the label and the cross of the remove button, while the chip is not selected. The theme's textSecondary.
borderColorColor.UnspecifiedThe chip's edge while it is not selected. The theme's border.
selectedContainerColorColor.UnspecifiedThe fill of a filter that is on. The primary role's container.
selectedContentColorColor.UnspecifiedThe label and the tick of a filter that is on: the colour that reads on selectedContainer. The primary role's onContainer.