Text & Typography

Text is the composable you'll write more than any other, and it looks deceptively simple — one required parameter, text, and everything else optional. But those optional parameters are where real UI polish lives: how the text is styled, whether it wraps, and what happens when a RecipeBox recipe title is simply too long for the space it's given.

Key Parameters: style, color, fontWeight

@Composable
fun SectionHeading(text: String) {
    Text(
        text = text,
        style = MaterialTheme.typography.titleLarge,
        color = MaterialTheme.colorScheme.primary,
        fontWeight = FontWeight.Bold
    )
}
  • style — a TextStyle bundling font size, line height, letter spacing, and default weight. Almost always sourced from MaterialTheme.typography rather than built by hand (see below).
  • color — overrides the text color from whatever style specifies. Sourcing it from MaterialTheme.colorScheme (rather than a hardcoded Color(0xFF...)) keeps it correct automatically in dark mode.
  • fontWeight — FontWeight.Normal, Medium, Bold, and more; layers on top of whatever the style already sets.

Use MaterialTheme.typography, Not Hardcoded Sizes

It's tempting to write fontSize = 22.sp directly, but that scatters magic numbers across every screen and makes a future type-scale redesign painful. Material 3 defines a Typography object with named presets — displayLarge, headlineSmall, titleMedium, bodyLarge, bodySmall, labelSmall, and more — accessible anywhere through MaterialTheme.typography:

// Avoid: a hardcoded, one-off size
Text(text = title, fontSize = 22.sp, fontWeight = FontWeight.Bold)

// Prefer: a named preset from the theme's type scale
Text(text = title, style = MaterialTheme.typography.titleLarge)

Using the preset means every recipe title across RecipeBox — list items, the detail screen, the add/edit form — stays visually consistent, and a single change to the app's Typography definition (covered later in Typography Customization) updates every screen at once.

maxLines and overflow

By default, Text wraps onto as many lines as it needs and never truncates. For a recipe list item, an unusually long title ("Grandma's Slow-Braised Sunday Pot Roast with Root Vegetables") would otherwise push the row's height around unpredictably. Constrain it with maxLines and tell Compose what to do with the leftover text via overflow:

@Composable
fun RecipeTitle(title: String, modifier: Modifier = Modifier) {
    Text(
        text = title,
        style = MaterialTheme.typography.titleMedium,
        fontWeight = FontWeight.Bold,
        maxLines = 1,
        overflow = TextOverflow.Ellipsis,
        modifier = modifier
    )
}

maxLines = 1 caps the title to a single line, and TextOverflow.Ellipsis replaces whatever text doesn't fit with a trailing … — so "Grandma's Slow-Braised Sunday Pot Roast with Root Vegetables" renders as something like "Grandma's Slow-Braised Sunday Pot…", and every row in the list stays exactly one line tall no matter how long its title is.

TextOverflow valueBehavior when text exceeds maxLines
TextOverflow.ClipText is cut off abruptly at the boundary, no ellipsis (the default).
TextOverflow.EllipsisCut off with a trailing … — what RecipeBox uses for titles.
TextOverflow.VisibleText is allowed to draw outside its own bounds, overlapping neighboring content.
Tip: You can detect truncation yourself with the onTextLayout callback (checking result.hasVisualOverflow) if you need to react to it — for example, showing a "read more" affordance only when a description actually got cut off.
Next up: Images (painterResource & Coil) covers loading RecipeBox's recipe photos, both bundled drawables and remote URLs.