Phase 1 · Getting Started with ComposeModule 8~45 min read

Text: Typography, Layout & Rich Content

Build readable text with theme typography, alignment, wrapping, ellipsis, styled spans and selection. Explore font scaling in an interactive lab.

Goals & prerequisites

A polished interface treats text as content with a purpose: a title establishes hierarchy, a summary supports a decision, and an error explains a recovery. This lesson turns Text from a label into a predictable, reusable part of your screen.

Complete Text and the Modifier System first. You need function parameters, named arguments and a generated Compose project using Material 3. The final exercise also uses state; return to it after the state lesson if those APIs are new.

  • Choose a theme style and override only the fields your design needs.
  • Predict how available width changes wrapping, alignment and truncation.
  • Display localized quantities and make useful reference text selectable.
  • Verify the result with long content, large fonts and TalkBack.

What Text actually does

Text displays a value supplied by its caller. It does not fetch a title, translate a hard-coded sentence, or decide which business message is correct. Keep those decisions outside a small display composable. When the supplied value changes, Compose can update the text; displaying the same value again does not append another label.

Separate three questions while reading a call: what is the content, which text style applies, and what space is available? A correct sentence with the wrong width can still produce a broken screen. Text is also distinct from TextField: choosing a text style never makes a label editable.

Parameters that matter

ParameterDecisionExample use
textPlain String or styled AnnotatedStringA title supplied by the screen
styleTypography role and reusable defaultsheadlineSmall for a section heading
color / fontWeightA deliberate override of the chosen styleAn emphasized quantity
modifierThe text box's placement and constraintsfillMaxWidth plus padding
textAlignCharacter alignment inside that boxStart, Center or End
maxLines / overflowWhat happens when content needs more roomTwo-line feed summary with ellipsis

Typography & inheritance

Start with MaterialTheme.typography so headings and body copy share an intentional scale. Material components can provide a local text style to their children; an explicit style selects your own base, while a parameter such as fontWeight overrides that field. This lets a reusable label inherit sensible defaults without repeating every setting.

TypographyChoices.kt
// Inside a @Composable function:
Text(
    text = "Your next lesson",
    style = MaterialTheme.typography.titleLarge,
    color = MaterialTheme.colorScheme.onSurface,
)

// The local style supplies defaults; an explicit parameter overrides a field.
ProvideTextStyle(MaterialTheme.typography.bodyMedium) {
    Text("Estimated study time")
    Text("25 minutes", fontWeight = FontWeight.SemiBold)
}
Focused fragments. Import ProvideTextStyle from androidx.compose.material3 and FontWeight from androidx.compose.ui.text.font.

Design for font scaling

Use sp when specifying a font size. Give text room to grow instead of placing a paragraph inside a fixed-height box. The user's font preference is an input to your layout, so test it as deliberately as you test a different sentence.

Width, wrapping & overflow

textAlign = TextAlign.Center aligns the characters inside the text's measured width. If that width barely fits the words, centering may appear to do nothing. Allocate width with a modifier when you intend a centered heading across a screen. Prefer Start and End for reading-direction-aware alignment.

TextBounds.kt
// Center the characters within an actual full-width text box.
Text(
    text = "Compose foundations",
    modifier = Modifier.fillMaxWidth(),
    textAlign = TextAlign.Center,
)

// A compact summary can truncate; the detail screen should expose the full text.
Text(
    text = summary,
    modifier = Modifier.fillMaxWidth(),
    maxLines = 2,
    overflow = TextOverflow.Ellipsis,
)
Focused fragments. TextAlign and TextOverflow belong to androidx.compose.ui.text.style; summary is the caller's String.

maxLines sets a visible limit; TextOverflow.Ellipsis indicates truncated content when it overflows. It does not shorten your underlying string. Use truncation for previews, then provide a way to read the full information. Do not truncate instructions that a user must follow to complete a task.

Rich text & selection

An AnnotatedString keeps a sentence in one text layout while allowing a span to look different. Use SpanStyle for character-level emphasis. Splitting every emphasized word into separate Text children in a Row makes normal sentence wrapping much harder.

EmphasizedSentence.kt
// Additional imports for this focused example:
import androidx.compose.ui.text.SpanStyle
import androidx.compose.ui.text.buildAnnotatedString
import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.text.withStyle

// Inside a @Composable function:
Text(
    text = buildAnnotatedString {
        append("Next: ")
        withStyle(SpanStyle(fontWeight = FontWeight.Bold)) {
            append("State and events")
        }
    },
    style = MaterialTheme.typography.bodyLarge,
)
A complete text expression with its additional imports. The fixed English sentence illustrates styling; localize real copy.

Style alone does not create a link or an action. For copyable order IDs, addresses or reference codes, wrap the relevant text in SelectionContainer. Do not enable selection indiscriminately around every action label. The reader should be able to distinguish an instruction, a reference and a button.

Strings & plurals

Add the following entries inside your existing app/src/main/res/values/strings.xml resource element. If creating a new resource file, use the full wrapper shown here. The generated R class belongs to your app's namespace; a file in another package needs an import for that app-specific R.

res/values/strings.xml
<resources>
    <string name="deep_text_title">Build interfaces with Jetpack Compose</string>
    <string name="deep_text_summary">Practice one clear concept, then explain what changed on screen.</string>
    <string name="deep_text_reference">Reference: COMPOSE-101</string>
    <plurals name="deep_text_exercises">
        <item quantity="one">%1$d exercise</item>
        <item quantity="other">%1$d exercises</item>
    </plurals>
</resources>

The two quantity arguments to pluralStringResource have different jobs: the first count selects the language's plural category; the second fills %1$d. English singular/plural rules are only one example, so let resources make that decision instead of concatenating a number and an English suffix.

Build a lesson header

Create LessonHeader.kt in the same package as MainActivity, keep the generated package line, then paste this code. Inside your existing setContent and generated app theme, replace the sample greeting call with LessonHeaderDemo(). Here, Column simply stacks its children vertically; use the supplied wrapper while focusing on the Text parameters. The upcoming layout lessons explain arrangement and scrolling in detail. The demo owns screen scrolling so a long paragraph remains reachable; the reusable header leaves scrolling to its containing screen. No additional dependency is needed beyond your Compose Material 3 template.

LessonHeader.kt
// Keep your project's existing package declaration above these imports.
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.rememberScrollState
import androidx.compose.foundation.text.selection.SelectionContainer
import androidx.compose.foundation.verticalScroll
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
import androidx.compose.ui.res.pluralStringResource
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.text.style.TextOverflow
import androidx.compose.ui.unit.dp

@Composable
fun LessonHeader(
    exerciseCount: Int,
    modifier: Modifier = Modifier,
) {
    require(exerciseCount >= 0)
    Column(
        modifier = modifier.fillMaxWidth().padding(20.dp),
        verticalArrangement = Arrangement.spacedBy(12.dp),
    ) {
        Text(
            text = stringResource(R.string.deep_text_title),
            style = MaterialTheme.typography.headlineSmall,
        )
        Text(
            text = stringResource(R.string.deep_text_summary),
            style = MaterialTheme.typography.bodyLarge,
            maxLines = 2,
            overflow = TextOverflow.Ellipsis,
        )
        Text(
            text = pluralStringResource(
                R.plurals.deep_text_exercises,
                exerciseCount,
                exerciseCount,
            ),
            style = MaterialTheme.typography.labelLarge,
            color = MaterialTheme.colorScheme.primary,
        )
        SelectionContainer {
            Text(
                text = stringResource(R.string.deep_text_reference),
                style = MaterialTheme.typography.bodyMedium,
            )
        }
    }
}

@Composable
fun LessonHeaderDemo() {
    Column(
        modifier = Modifier
            .fillMaxSize()
            .verticalScroll(rememberScrollState()),
    ) {
        LessonHeader(exerciseCount = 3)
    }
}
Complete reusable header, scrolling demo wrapper and explicit imports; uses the resource entries above.

Expect a wrapping headline, a compact summary, “3 exercises” and a selectable reference. Change the count to one and verify the noun changes. The root uses the caller's modifier exactly once, leaving the containing screen in control of placement.

Explore the behavior

Change the controls and predict which text will wrap or truncate before looking at the preview. This browser demonstration approximates the visual idea; Android font metrics, semantics and actual Compose measurement must be checked in Android Studio and on a device.

Composable lab · Change & observe

Give Text different constraints

Text bounds

Build thoughtful Android experiences with Jetpack Compose.

Predict, then compare. Set 2× font scale, then remove the line limit. The container grows to keep the whole message readable. Alignment moves text inside its bounds.
Text(
    text = "Build thoughtful Android experiences with Jetpack Compose.",
    modifier = Modifier.width(240.dp),
    fontSize = 18.sp,
    textAlign = TextAlign.Start,
    maxLines = 2,
    overflow = TextOverflow.Ellipsis,
)
// Font scale is a device setting, not a Text parameter.

Interactive browser model with a Kotlin configuration excerpt. It does not run Compose. Font metrics, scaling, touch targets, focus, and assistive technology must be checked on an Android device or emulator.

Accessibility & diagnosis

SymptomInspect firstUseful correction
Center alignment seems ineffectiveDoes the text box have spare width?Give the box the intended width before aligning its characters.
Letters disappear with large fontsFixed height on Text or an ancestorLet content measure its height; test the full screen at large font settings.
Dark mode makes a caption unreadableHard-coded foreground and backgroundUse compatible theme color roles and inspect contrast.
TalkBack repeats a sentenceAn unnecessary contentDescription duplicating visible textLet the Text semantics expose its readable content.

Read the whole header with TalkBack, then test the longest plausible translated title. A successful layout preserves meaning rather than merely avoiding a crash. Color can reinforce hierarchy, but the words and placement should communicate it too.

Practice & worked solution

  1. Run the worked example with zero, one and seven exercises. Record the exact visible quantity labels.
  2. Replace the summary resource with a much longer paragraph and verify the two-line preview at different font sizes.
  3. After completing the state lesson, return to add the expand/collapse action below. Increase system font size and rotate the device, then confirm the full summary can be reached.

Acceptance checks: the initial summary is at most two lines, expanding exposes the full string, collapsing restores the limit, and the button announces its current action. The reference remains selectable. The exercise does not require a network connection.

Reveal one worked solution

Use one Boolean as the source of truth for both the line limit and the action label. Keeping these expressions together prevents a label that says “Show less” while the content is still collapsed.

LessonHeader-expansion.kt
// Add these imports to LessonHeader.kt:
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.saveable.rememberSaveable
import androidx.compose.runtime.setValue
import androidx.compose.material3.TextButton

// Inside LessonHeader, before Column:
var expanded by rememberSaveable { mutableStateOf(false) }

// Replace the summary Text with these siblings inside Column:
Text(
    text = stringResource(R.string.deep_text_summary),
    style = MaterialTheme.typography.bodyLarge,
    maxLines = if (expanded) Int.MAX_VALUE else 2,
    overflow = TextOverflow.Ellipsis,
)
TextButton(onClick = { expanded = !expanded }) {
    Text(if (expanded) "Show less" else "Read full summary")
}
// Move those two button labels into strings.xml for localization.
Patch to the complete example. rememberSaveable is introduced in the course's state lesson.

Quick check & references

Key takeaways

  • Choose content, typography and bounds as three separate decisions.
  • Use theme typography and deliberate overrides; give scaled text enough height.
  • A line limit controls the preview, so provide access to complete content when needed.
  • Use resources for language decisions and selection for useful copyable text.

Quick check

1. A short title still looks left-aligned after textAlign = Center. What should you inspect?

2. Which behavior does an ellipsis guarantee?

3. Why pass the same count twice to a formatted plural resource?

Continue with the official guides to text styling, text layout, selection and interactions, and Compose resources.