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
| Parameter | Decision | Example use |
|---|---|---|
text | Plain String or styled AnnotatedString | A title supplied by the screen |
style | Typography role and reusable defaults | headlineSmall for a section heading |
color / fontWeight | A deliberate override of the chosen style | An emphasized quantity |
modifier | The text box's placement and constraints | fillMaxWidth plus padding |
textAlign | Character alignment inside that box | Start, Center or End |
maxLines / overflow | What happens when content needs more room | Two-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.
// 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)
}Design for font scaling
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.
// 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,
)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.
// 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,
)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.
<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.
// 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)
}
}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.
Give Text different constraints
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
| Symptom | Inspect first | Useful correction |
|---|---|---|
| Center alignment seems ineffective | Does the text box have spare width? | Give the box the intended width before aligning its characters. |
| Letters disappear with large fonts | Fixed height on Text or an ancestor | Let content measure its height; test the full screen at large font settings. |
| Dark mode makes a caption unreadable | Hard-coded foreground and background | Use compatible theme color roles and inspect contrast. |
| TalkBack repeats a sentence | An unnecessary contentDescription duplicating visible text | Let 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
- Run the worked example with zero, one and seven exercises. Record the exact visible quantity labels.
- Replace the summary resource with a much longer paragraph and verify the two-line preview at different font sizes.
- 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.
// 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.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.