Pantrix UI

Text

Text in the theme's type. style is the base text unless given, LocalPantrixTextStyle: the label role, or what a part of the screen around it provides. role tells a screen reader what the text is: a heading, a label, decoration. selectable lets the user select and copy it.

import com.pantrixui.components.elements.text.PantrixText

Examples

A text given no style

The least there is to write: the text alone. Given no style, it is set in the theme's label style; given no colour, it is drawn in the theme's textPrimary colour. The second line is the same text with the label style given outright: the two are the same.

label is the style of a control's label. Giving a paragraph body, or a heading title or headline, is up to you: the third line is set in body.

PantrixText(text = "Your order is on its way", tagKey = "no_style")
PantrixText(text = "Your order is on its way", style = PantrixTheme.typography.label)
PantrixText(text = "Your order is on its way", style = PantrixTheme.typography.body)

Type scale

The theme's nine styles, largest to smallest: each line is set in its own style and says what that style is for. A style carries the type's size, its line height, its weight and its letter spacing together.

code is set in a monospace face. The styles' metrics are written on the "Typography" page, read from the theme.

val typography = PantrixTheme.typography
PantrixText(text = "display: the page's title", style = typography.display)
PantrixText(text = "headline: a section heading", style = typography.headline)
PantrixText(text = "title: the title of a card, a panel, a dialog", style = typography.title)
PantrixText(text = "callout: emphasised body text, the description of an empty screen", style = typography.callout)
PantrixText(text = "body: body text, a paragraph, a description", style = typography.body)
PantrixText(text = "label: a control's label, a text given no style", style = typography.label)
PantrixText(text = "footnote: supporting text, the description under a field", style = typography.footnote)
PantrixText(text = "caption: a small detail, a time, a counter", style = typography.caption)
PantrixText(text = "code: order_id = 8F2A-19C4", style = typography.code)

Roles

role says what the text is; style says how it looks. TextRole.Heading reports the text to a screen reader as a heading: a user who moves from heading to heading with the screen reader stops at it. level is the heading's depth: 1 for the page's title, 2 for its sections. A text set large and bold but given no role is plain prose to a screen reader.

Paragraph and Label are read as they are. A Decorative text is hidden from the screen reader and its words are never read: it is for text that repeats what something else already says. Here the large "%72" is a picture of the sentence under it; the screen reader reads only the sentence.

val typography = PantrixTheme.typography
PantrixText(
    text = "Account",
    style = typography.headline,
    role = TextRole.Heading(level = 1),
    tagKey = "heading_account"
)
PantrixText(
    text = "Storage",
    style = typography.title,
    role = TextRole.Heading(level = 2),
    tagKey = "heading_storage"
)
PantrixText(text = "%72", style = typography.display, role = TextRole.Decorative, tagKey = "share")
PantrixText(text = "72 per cent of the storage is full.", style = typography.body)
Row(horizontalArrangement = Arrangement.spacedBy(PantrixTheme.spacing.sm)) {
    PantrixText(text = "Plan", role = TextRole.Label)
    PantrixText(text = "Monthly, 50 GB", color = PantrixTheme.colors.textSecondary)
}

Colour, and the colour of where it stands

On the page, a text given no colour is drawn in the theme's textPrimary colour. color changes that: textSecondary for a secondary detail, the danger role's colour for an error.

On a fill, still no colour is given. When a PantrixSurface is filled with a role's colour, the text inside takes the colour that reads on that fill by itself: primary.onColor in the first box, success.onContainer in the second. The theme's own surface is not a fill but a ground: the text in the third box is drawn in textPrimary again.

val colors = PantrixTheme.colors
val inside = Modifier.padding(PantrixTheme.spacing.md)
PantrixText(text = "No colour given")
PantrixText(text = "Last updated: 2 minutes ago", color = colors.textSecondary)
PantrixText(text = "Invalid card number", color = colors.danger.color, tagKey = "error")
FlowRow(
    horizontalArrangement = Arrangement.spacedBy(PantrixTheme.spacing.sm),
    verticalArrangement = Arrangement.spacedBy(PantrixTheme.spacing.sm)
) {
    PantrixSurface(color = colors.primary.color) {
        PantrixText(text = "On the primary fill", modifier = inside)
    }
    PantrixSurface(color = colors.success.container) {
        PantrixText(text = "On the success.container fill", modifier = inside)
    }
    PantrixSurface(border = colors.divider) {
        PantrixText(text = "On the theme's surface", modifier = inside)
    }
}

Maximum lines and overflow

The same paragraph three times, in a column at most 320dp wide. The first takes as many lines as it wants. The second stays at two lines with maxLines = 2, and what does not fit ends in an ellipsis: the default of overflow is TextOverflow.Ellipsis. The third is one line and, with TextOverflow.Clip, is cut without an ellipsis.

val paragraph = "Your order has been shipped. It will reach the delivery address tomorrow between 09:00 and 18:00; the courier " +
    "will call you before coming to the door."
Column(
    modifier = Modifier.widthIn(max = 320.dp),
    verticalArrangement = Arrangement.spacedBy(PantrixTheme.spacing.md)
) {
    PantrixText(text = paragraph, style = PantrixTheme.typography.body)
    PantrixText(text = paragraph, style = PantrixTheme.typography.body, maxLines = 2, tagKey = "two_lines")
    PantrixText(
        text = paragraph,
        style = PantrixTheme.typography.body,
        maxLines = 1,
        overflow = TextOverflow.Clip,
        tagKey = "clipped"
    )
}

Minimum lines

minLines makes even a short text take the room of that many lines. The titles of the two boxes that stand side by side are set with minLines = 2 and maxLines = 2: one fits on one line and the other is longer than two, but both take two lines of room. So the prices under them line up, and the boxes are the same height.

val colors = PantrixTheme.colors
val spacing = PantrixTheme.spacing
Row(
    modifier = Modifier.widthIn(max = 420.dp),
    horizontalArrangement = Arrangement.spacedBy(spacing.md)
) {
    PantrixSurface(modifier = Modifier.weight(1f), border = colors.divider) {
        Column(modifier = Modifier.padding(spacing.md), verticalArrangement = Arrangement.spacedBy(spacing.xs)) {
            PantrixText(text = "Tea", style = PantrixTheme.typography.callout, maxLines = 2, minLines = 2)
            PantrixText(text = "45,00 TL", color = colors.textSecondary)
        }
    }
    PantrixSurface(modifier = Modifier.weight(1f), border = colors.divider) {
        Column(modifier = Modifier.padding(spacing.md), verticalArrangement = Arrangement.spacedBy(spacing.xs)) {
            PantrixText(
                text = "Cold-brewed filter coffee with cinnamon and orange",
                style = PantrixTheme.typography.callout,
                maxLines = 2,
                minLines = 2
            )
            PantrixText(text = "120,00 TL", color = colors.textSecondary)
        }
    }
}

Alignment

textAlign aligns the lines within the text's own width. That is why the texts are given fillMaxWidth(): a text only as wide as its words has no room to align them in.

PantrixText(text = "Aligned to the start", modifier = Modifier.fillMaxWidth(), textAlign = TextAlign.Start)
PantrixText(text = "Centred", modifier = Modifier.fillMaxWidth(), textAlign = TextAlign.Center)
PantrixText(text = "Aligned to the end", modifier = Modifier.fillMaxWidth(), textAlign = TextAlign.End)

Selectable text

With selectable = true the user can select the text and copy it. It is for text that will be taken somewhere else: an order number, an IBAN, an error code. A screen reader reads it as it reads any other text.

Try to select the number on the second line; the label above it cannot be selected.

PantrixText(text = "Order number", style = PantrixTheme.typography.footnote, role = TextRole.Label)
PantrixText(
    text = "PX-2026-0001-8F2A-19C4",
    style = PantrixTheme.typography.code,
    selectable = true,
    tagKey = "order_number"
)

Styled text

The PantrixText that takes an AnnotatedString: parts styled differently inside one text. Its style, its colour, its lines and its role are the same as a plain text's.

In the first line the decimal part of the amount is set at 0.6.em: an em is a share of the size of the text around it. In the second line one part is bold, and one is in the danger role's colour. The third line has a link; it is underlined, because someone who cannot tell colours apart cannot see a link that differs from its sentence by colour alone. Press the link: the app's snackbar reports it.

val colors = PantrixTheme.colors
val snackbars = LocalPantrixSnackbars.current
val scope = rememberCoroutineScope()
val price = buildAnnotatedString {
    append("1.250")
    withStyle(SpanStyle(fontSize = 0.6.em)) { append(",00 TL") }
}
val notice = buildAnnotatedString {
    append("On ")
    withStyle(SpanStyle(fontWeight = FontWeight.Bold)) { append("28 October") }
    append(" your payment will be taken. ")
    withStyle(SpanStyle(color = colors.danger.color)) { append("Your card is about to expire.") }
}
val linkStyles = TextLinkStyles(
    style = SpanStyle(color = colors.primary.color, textDecoration = TextDecoration.Underline)
)
val terms = buildAnnotatedString {
    append("By continuing you agree that the ")
    withLink(
        LinkAnnotation.Clickable(tag = "terms", styles = linkStyles) {
            scope.launch { snackbars?.show(message = "Terms of use opened") }
        }
    ) {
        append("terms of use")
    }
    append(" apply.")
}
PantrixText(text = price, style = PantrixTheme.typography.headline, tagKey = "price")
PantrixText(text = notice, style = PantrixTheme.typography.body)
PantrixText(text = terms, style = PantrixTheme.typography.body, tagKey = "terms")

An icon inside the line

inlineContent puts something that is not text inside the line. A place is kept in the text with appendInlineContent; inlineContent says, for that place's key, what is drawn there and how much room it takes. Here there is an icon in the middle of the sentence: it stands on the same line as the words.

The second value of appendInlineContent is what stands for that place in the text: a screen reader reads the icon by this word.

val hint = buildAnnotatedString {
    append("To find a product, press the ")
    appendInlineContent(id = "search", alternateText = "search")
    append(" button.")
}
PantrixText(
    text = hint,
    style = PantrixTheme.typography.body,
    inlineContent = mapOf(
        "search" to InlineTextContent(
            placeholder = Placeholder(
                width = 1.25.em,
                height = 1.25.em,
                placeholderVerticalAlign = PlaceholderVerticalAlign.TextCenter
            )
        ) {
            PantrixIcon(imageVector = PantrixIcons.Search, contentDescription = null, size = 20.dp)
        }
    ),
    tagKey = "hint"
)

API

text

Text in the theme's type. style is the base text unless given, LocalPantrixTextStyle: the label role, or what a part of the screen around it provides. role tells a screen reader what the text is: a heading, a label, decoration. selectable lets the user select and copy it.

ParameterTypeDefaultWhat it is
textStringthe text.
modifierModifierModifierthe text's modifier.
styleTextStyleLocalPantrixTextStyle.current
colorColorColor.Unspecified
textAlignTextAlign?nullhow the lines are aligned in the text's width.
maxLinesIntInt.MAX_VALUEthe most lines the text takes; what goes past them is cut as overflow says.
minLinesInt1the least lines the text is tall.
overflowTextOverflowTextOverflow.Ellipsis
softWrapBooleantruewhether the text wraps where its width ends.
roleTextRoleTextRole.Paragraph
selectableBooleanfalse
onTextLayout((TextLayoutResult) -> Unit)?nullcalled with the text as it is laid out.
tagKeyString?nullthe text's automation key: the text is Text_<tagKey>. Without one it has no tag.

text

PantrixText for an AnnotatedString, with its spans and links, and inlineContent for the placeholders it names.

ParameterTypeDefaultWhat it is
textAnnotatedStringthe text, with its spans and links.
modifierModifierModifierthe text's modifier.
styleTextStyleLocalPantrixTextStyle.currentthe base text: LocalPantrixTextStyle unless given, the label role or what a part of the screen around it provides.
colorColorColor.Unspecifiedthe text's colour, or else the style's, or else the theme's textPrimary; on a fill, the colour written there.
textAlignTextAlign?nullhow the lines are aligned in the text's width.
maxLinesIntInt.MAX_VALUEthe most lines the text takes; what goes past them is cut as overflow says.
minLinesInt1the least lines the text is tall.
overflowTextOverflowTextOverflow.Ellipsishow a text longer than maxLines is cut: with an ellipsis unless told.
softWrapBooleantruewhether the text wraps where its width ends.
roleTextRoleTextRole.Paragraphwhat the text is, for a screen reader: prose, a heading, a label or decoration.
selectableBooleanfalsewhether the user can select the text and copy it.
inlineContentMap<String, InlineTextContent>emptyMap()
onTextLayout((TextLayoutResult) -> Unit)?nullcalled with the text as it is laid out.
tagKeyString?nullthe text's automation key: the text is Text_<tagKey>. Without one it has no tag.