Pantrix UI

Badge

A count or a status dot, usually put on another element with PantrixBadgedBox.

import com.pantrixui.components.elements.badge.PantrixBadge

Examples

Dot and count

Without a count the badge is a dot; with one it is a pill that grows wider with its number. A number of one digit is a full circle, and the pill is never narrower than it is tall.

Row(
    horizontalArrangement = Arrangement.spacedBy(PantrixTheme.spacing.md),
    verticalAlignment = Alignment.CenterVertically
) {
    PantrixBadge()
    PantrixBadge(count = 3)
    PantrixBadge(count = 12)
    PantrixBadge(count = 128)
}

A count that overflows

A number above maxCount is not written; the limit and a plus are written in its place: the default limit is 99. The pill stops growing there, and does not cover the icon it sits on. The last badge's limit is 9.

Row(
    horizontalArrangement = Arrangement.spacedBy(PantrixTheme.spacing.md),
    verticalAlignment = Alignment.CenterVertically
) {
    PantrixBadge(count = 99)
    PantrixBadge(count = 100)
    PantrixBadge(count = 2500)
    PantrixBadge(count = 12, maxCount = 9)
}

Tones

tone fills the badge with the colour of one of the theme's roles, and writes the number in the colour that reads on it. The default tone is Danger: a badge mostly tells of something that needs looking at. The top row has a number in every tone, and the one under it a dot in every tone.

FlowRow(
    horizontalArrangement = Arrangement.spacedBy(PantrixTheme.spacing.md),
    verticalArrangement = Arrangement.spacedBy(PantrixTheme.spacing.sm),
    itemVerticalAlignment = Alignment.CenterVertically
) {
    BadgeTone.entries.forEachIndexed { index, tone ->
        PantrixBadge(count = TONE_COUNTS[index], tone = tone, tagKey = "tone_${tone.name}")
    }
}
Row(horizontalArrangement = Arrangement.spacedBy(PantrixTheme.spacing.md)) {
    BadgeTone.entries.forEach { tone -> PantrixBadge(tone = tone) }
}

private val TONE_COUNTS = listOf(1, 12, 5, 8, 120, 0)

On the corner of an icon

PantrixBadgedBox sets the badge on a corner of its content: by default the top corner on the side where lines end.

The badge is hidden from screen readers; the icon's own description says the number ("Inbox, 5 unread"). When the number gets down to zero no badge is put there at all: it is the caller that does this, not the badge. Raise the number with the buttons: past 99 it reads "99+", and the pill grows away from the icon.

var unread by rememberSaveable { mutableIntStateOf(START_UNREAD) }
Row(
    horizontalArrangement = Arrangement.spacedBy(PantrixTheme.spacing.lg),
    verticalAlignment = Alignment.CenterVertically
) {
    PantrixBadgedBox(badge = { if (unread > 0) PantrixBadge(count = unread, tagKey = "unread") }) {
        PantrixIcon(
            imageVector = PantrixIcons.Inbox,
            contentDescription = if (unread > 0) "Inbox, $unread unread" else "Inbox",
            tagKey = "inbox"
        )
    }
    PantrixButton(
        text = "+1",
        onClick = { unread += 1 },
        variant = ButtonVariant.Outline,
        size = ButtonSize.Small,
        tagKey = "one_more"
    )
    PantrixButton(
        text = "+50",
        onClick = { unread += MANY },
        variant = ButtonVariant.Outline,
        size = ButtonSize.Small,
        tagKey = "many_more"
    )
    PantrixButton(
        text = "Mark as read",
        onClick = { unread = 0 },
        variant = ButtonVariant.Text,
        size = ButtonSize.Small,
        tagKey = "read"
    )
}

private const val START_UNREAD = 5

private const val MANY = 50

On avatars

On round content the badge stands on the circle, not on the corner of the box: overlap = BadgeOverlap.Circular. alignment says which corner; the status dot is at the bottom, on the side where lines end.

The status dots are made larger with BadgeStyle(dotSize = 12.dp): large enough to be made out on an avatar. The avatar's description says what the dot means ("İbrahim Öztürk, online"); the dot is hidden from screen readers.

val statusDot = BadgeStyle(dotSize = 12.dp)
Row(horizontalArrangement = Arrangement.spacedBy(PantrixTheme.spacing.xl)) {
    PantrixBadgedBox(
        badge = { PantrixBadge(count = 2, tagKey = "messages") },
        overlap = BadgeOverlap.Circular
    ) {
        PantrixAvatar(contentDescription = "Ayşe Yılmaz, 2 new messages", initials = "AY", tagKey = "badged_ayse")
    }
    PantrixBadgedBox(
        badge = { PantrixBadge(tone = BadgeTone.Success, style = statusDot, tagKey = "online") },
        alignment = Alignment.BottomEnd,
        overlap = BadgeOverlap.Circular
    ) {
        PantrixAvatar(
            contentDescription = "İbrahim Öztürk, online",
            initials = "İÖ",
            tone = AvatarTone.Info,
            tagKey = "badged_ibrahim"
        )
    }
    PantrixBadgedBox(
        badge = { PantrixBadge(tone = BadgeTone.Neutral, style = statusDot, tagKey = "offline") },
        alignment = Alignment.BottomEnd,
        overlap = BadgeOverlap.Circular
    ) {
        PantrixAvatar(
            contentDescription = "ilker kaya, offline",
            initials = "ik",
            tone = AvatarTone.Success,
            tagKey = "badged_ilker"
        )
    }
    PantrixBadgedBox(
        badge = { PantrixBadge(count = MANY * 3, tagKey = "team") },
        overlap = BadgeOverlap.Circular
    ) {
        PantrixAvatar(
            contentDescription = "Team, more than 99 new messages",
            icon = PantrixIcons.Person,
            tone = AvatarTone.Primary,
            tagKey = "badged_team"
        )
    }
}

private const val MANY = 50

A colour and a description of its own

colors gives the badge a fill of the given colour, not of a tone: here the app's own purple, which is in none of the theme's roles. Only the fields that are set change; the tone still decides the rest.

These badges stand alone, not on an icon. So they say what they mean themselves: each is given a contentDescription, and a screen reader reads "4 new offers".

val own = ownColors()
Row(
    horizontalArrangement = Arrangement.spacedBy(PantrixTheme.spacing.md),
    verticalAlignment = Alignment.CenterVertically
) {
    PantrixBadge(count = 3, contentDescription = "3 new notifications", tagKey = "described")
    PantrixBadge(
        count = 4,
        colors = BadgeColors(container = own.color, content = own.onColor),
        contentDescription = "4 new offers",
        tagKey = "own_colors"
    )
}

Beside a text

A badge can stand on a text too. PantrixBadgedBox keeps the baseline of its content: with alignByBaseline the "new" label stands on the same line as the title beside it, and the dot on the label's corner.

Row(horizontalArrangement = Arrangement.spacedBy(PantrixTheme.spacing.sm)) {
    PantrixText(
        text = "Campaigns",
        modifier = Modifier.alignByBaseline(),
        style = PantrixTheme.typography.callout
    )
    PantrixBadgedBox(
        badge = { PantrixBadge(tone = BadgeTone.Primary, tagKey = "new") },
        modifier = Modifier.alignByBaseline()
    ) {
        PantrixText(text = "new", style = PantrixTheme.typography.footnote)
    }
}

API

modifier

A count or a status dot, usually put on another element with PantrixBadgedBox.

ParameterTypeDefaultWhat it is
modifierModifierModifierthe badge's modifier.
countInt?nullthe number to show, or null for a dot.
maxCountInt99the largest number that is written out. It has to be positive.
toneBadgeToneBadgeTone.Dangerthe colour role the badge is filled from.
colorsBadgeColors?nullthis badge's own colours, in place of its tone's: the ones it sets, with the tone's for the rest.
styleBadgeStyle?nullthis call's own style: over every enclosing one and under the density. Only the fields it sets change, on every platform.
contentDescriptionString?nullwhat a screen reader says for the badge, or null to hide the badge from it.
tagKeyString?nullthe badge's automation key: it is Badge_<tagKey>, and is tagged only when given one.

BadgeStyle

PantrixBadge's geometry: a dot where there is nothing to count, and otherwise a pill that grows with its number.

ParameterTypeDefaultWhat it is
dotSizeDpDp.UnspecifiedThe dot's width and height.
heightDpDp.UnspecifiedThe pill's height. A number set larger than it has room for, at a large font scale, makes the pill taller.
minWidthDpDp.UnspecifiedThe pill's least width. The pill is never narrower than it is tall, whatever this says.
horizontalPaddingDpDp.UnspecifiedThe room on each side of the number, which is what makes the pill wider than it is tall as the number grows.

BadgeColors

What PantrixBadge is painted with, for a badge that does not take its tone's colours: a colour that is set is used, and one that is left out stays the tone's.

ParameterTypeDefaultWhat it is
containerColorColor.UnspecifiedThe badge's fill, and the whole of a dot. The color of the tone's role.
contentColorColor.UnspecifiedThe number: the colour that reads on container. The onColor of the tone's role.

Accessibility