DEV Community

myougaTheAxo
myougaTheAxo

Posted on

Text Styling in Compose — AnnotatedString, SpanStyle & Rich Text

Text Styling in Compose — AnnotatedString, SpanStyle & Rich Text

Create rich text with inline styling, clickable regions, and advanced text control in Jetpack Compose.

Typography and Basic Styling

@Composable
fun TypographyExample() {
    Column(modifier = Modifier.padding(16.dp)) {
        Text(
            "Headline",
            style = MaterialTheme.typography.headlineSmall
        )
        Text(
            "Body text with default styling",
            style = MaterialTheme.typography.bodyMedium
        )
        Text(
            "Caption",
            style = MaterialTheme.typography.labelSmall
        )
    }
}
Enter fullscreen mode Exit fullscreen mode

AnnotatedString with SpanStyle

@Composable
fun StyledTextExample() {
    val annotated = buildAnnotatedString {
        append("This is ")
        withStyle(style = SpanStyle(fontWeight = FontWeight.Bold)) {
            append("bold text")
        }
        append(" and this is ")
        withStyle(style = SpanStyle(color = Color.Red)) {
            append("red text")
        }
        append(". Here's some ")
        withStyle(
            style = SpanStyle(
                background = Color.Yellow,
                color = Color.Black
            )
        ) {
            append("highlighted")
        }
        append(" text.")
    }

    Text(annotated)
}
Enter fullscreen mode Exit fullscreen mode

ClickableText with URL Annotations

@Composable
fun ClickableTextExample() {
    val annotated = buildAnnotatedString {
        append("Visit our ")

        pushStringAnnotation(
            tag = "URL",
            annotation = "https://example.com"
        )
        withStyle(style = SpanStyle(color = Color.Blue, textDecoration = TextDecoration.Underline)) {
            append("website")
        }
        pop()

        append(" for more info.")
    }

    ClickableText(
        text = annotated,
        onClick = { offset ->
            annotated.getStringAnnotations(
                tag = "URL",
                start = offset,
                end = offset
            ).firstOrNull()?.let { annotation ->
                println("Clicked: ${annotation.item}")
                // Open browser
            }
        }
    )
}
Enter fullscreen mode Exit fullscreen mode

ParagraphStyle for Layout Control

@Composable
fun ParagraphStyleExample() {
    val annotated = buildAnnotatedString {
        withStyle(
            style = ParagraphStyle(
                textAlign = TextAlign.Center,
                lineHeight = 20.sp
            )
        ) {
            append("Centered paragraph\nwith custom line height")
        }

        append("\n\n")

        withStyle(
            style = ParagraphStyle(
                textAlign = TextAlign.Justify,
                textIndent = TextIndent(firstLine = 8.sp)
            )
        ) {
            append("Indented justified text starts here with 8sp indent.")
        }
    }

    Text(annotated, modifier = Modifier.padding(16.dp))
}
Enter fullscreen mode Exit fullscreen mode

SelectionContainer and DisableSelection

@Composable
fun SelectableTextExample() {
    Column(modifier = Modifier.padding(16.dp)) {
        SelectionContainer {
            Column {
                Text("This text is selectable")

                DisableSelection {
                    Text("This text is NOT selectable")
                }

                Text("This is selectable again")
            }
        }
    }
}
Enter fullscreen mode Exit fullscreen mode

Text Overflow with Expand/Collapse

@Composable
fun ExpandableTextExample() {
    var isExpanded by remember { mutableStateOf(false) }

    val displayText = if (isExpanded) {
        "This is the full text with all details. " +
        "Lorem ipsum dolor sit amet, consectetur adipiscing elit. " +
        "Sed do eiusmod tempor incididunt ut labore et dolore magna aliqua."
    } else {
        "This is the full text with all details..."
    }

    Column(modifier = Modifier.padding(16.dp)) {
        Text(
            text = displayText,
            maxLines = if (isExpanded) Int.MAX_VALUE else 2,
            overflow = TextOverflow.Ellipsis,
            modifier = Modifier.fillMaxWidth()
        )

        TextButton(onClick = { isExpanded = !isExpanded }) {
            Text(if (isExpanded) "Show Less" else "Show More")
        }
    }
}
Enter fullscreen mode Exit fullscreen mode

Complex Rich Text Example

@Composable
fun RichTextArticle() {
    val content = buildAnnotatedString {
        withStyle(style = SpanStyle(fontSize = 24.sp, fontWeight = FontWeight.Bold)) {
            append("Article Title")
        }
        append("\n\n")

        withStyle(style = SpanStyle(color = Color.Gray, fontSize = 12.sp)) {
            append("By Author • March 2, 2025")
        }
        append("\n\n")

        withStyle(style = ParagraphStyle(lineHeight = 1.5.em)) {
            append("This is the opening paragraph with ")
            withStyle(style = SpanStyle(fontStyle = FontStyle.Italic)) {
                append("italic emphasis")
            }
            append(" and a ")

            pushStringAnnotation("LINK", "https://example.com")
            withStyle(style = SpanStyle(color = Color.Blue, textDecoration = TextDecoration.Underline)) {
                append("clickable link")
            }
            pop()

            append(".")
        }
    }

    SelectionContainer {
        Text(
            text = content,
            modifier = Modifier
                .fillMaxWidth()
                .padding(16.dp)
        )
    }
}
Enter fullscreen mode Exit fullscreen mode

Key Takeaways

  • Use AnnotatedString for mixed styling within single Text
  • Apply SpanStyle for character-level formatting (bold, color, background)
  • Use ParagraphStyle for block-level control (alignment, indentation)
  • ClickableText enables interactive regions with annotations
  • SelectionContainer makes text selectable; DisableSelection for specific regions
  • Implement expand/collapse with maxLines and TextOverflow.Ellipsis

8 Android app templates: Gumroad

Top comments (0)