Support Large Font Sizes and Font Scaling in Compose

Quick answer: Use
spfor text and line height, keep text containers flexible in both width and height, and test the actual screen at Android’s largest font setting. Do not shrink, clip, or calculate text back to a fixed size fromfontScale. Let Compose apply the user’s system preference, then make the layout reflow: wrap text, move actions below it when needed, and preserve every essential label and control.
Large text is a real layout mode, not a cosmetic zoom. A screen can look polished at the default font size yet hide labels, overlap actions, or trap content inside a fixed-height card when text grows. Compose already receives the system font preference; the work is to give that text room to occupy.
Respect Android’s font-scale model
Compose text sized in sp responds to the user’s preferred font size. This is one reason to use MaterialTheme.typography and normal TextStyle values instead of converting a text size into dp or pixels yourself.
Android 14 supports font scaling up to 200% and uses non-linear scaling at larger settings. Large display styles do not necessarily grow by the same proportional amount as small body styles. The Android 14 accessibility guidance therefore recommends sp for text and warns against equations based on Configuration.fontScale or DisplayMetrics.scaledDensity.
| Use | Unit or approach | Why |
|---|---|---|
| Font size | sp | Follows the system font-size preference. |
| Line height | sp or relative em | Grows with the text instead of becoming cramped. |
| Padding, spacing, icon size, touch targets | dp | Keeps layout and interaction measurements independent from text scaling. |
| Typography role | MaterialTheme.typography | Preserves a coherent hierarchy across screens. |
Do not write code such as 16.sp * LocalDensity.current.fontScale to “support” large text. It applies a second scale and assumes a linear relationship that modern Android does not use. Similarly, do not cap a user’s font size because a compact layout looks tidier; rework the layout that cannot accommodate it.
Put scalable text in the type system
Named Material 3 typography styles are a useful default because the font family, size, weight, and line height live in one reviewable place. Define explicit line height for reading text and keep it in a scalable text unit.
import androidx.compose.material3.Typography
import androidx.compose.ui.text.TextStyle
import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.unit.em
import androidx.compose.ui.unit.sp
val AppTypography = Typography(
titleLarge = TextStyle(
fontWeight = FontWeight.SemiBold,
fontSize = 22.sp,
lineHeight = 1.25.em,
),
bodyLarge = TextStyle(
fontSize = 16.sp,
lineHeight = 1.5.em,
),
)Using em makes line height relative to the text size; sp is also suitable when the type scale specifies an absolute relationship. What matters is that line height is not in dp or pixels. Android’s text paragraph guidance documents em as a relative line-height option, while its font-scaling guidance explains why text measurements must not be treated as simple dp arithmetic.
Feature UI can then choose a semantic role rather than invent a separate text size:
Text(
text = article.title,
style = MaterialTheme.typography.titleLarge,
)For the theme-level setup, custom font mapping, and type roles, see Custom Fonts and Typography in Jetpack Compose. This article focuses on the next responsibility: letting the rest of the screen make space when those roles grow.
Replace fixed text boxes with flexible layout
The most common large-font failure is a fixed height placed around unknown text. Avoid a height(56.dp) row that must contain a localized title, summary, badge, and action. Let the row grow vertically and reserve fixed measurements only for things that truly need them, such as a 48dp icon-button target.
This article row keeps the title flexible and gives the independent overflow action a stable touch target:
@Composable
fun SavedArticleRow(
article: ArticleUi,
onOpen: () -> Unit,
onMore: () -> Unit,
) {
Row(
modifier = Modifier
.fillMaxWidth()
.clickable(onClick = onOpen)
.padding(start = 16.dp, top = 12.dp, bottom = 12.dp),
verticalAlignment = Alignment.Top,
) {
Column(Modifier.weight(1f)) {
Text(
text = article.title,
style = MaterialTheme.typography.titleMedium,
)
Spacer(Modifier.height(4.dp))
Text(
text = article.summary,
style = MaterialTheme.typography.bodyMedium,
color = MaterialTheme.colorScheme.onSurfaceVariant,
)
}
IconButton(onClick = onMore) {
Icon(
imageVector = Icons.Outlined.MoreVert,
contentDescription = stringResource(R.string.article_options),
)
}
}
}weight(1f) gives text the remaining width without forcing it onto one line. The Column can grow downward; IconButton remains a separate, reachable action. If both taps perform the same task, make the whole row one action instead—Touch Targets, Focus Order, and TalkBack Support explains when one parent target is appropriate.
Reflow actions instead of squeezing them beside text
At the default font size, a horizontal action row can be compact and easy to scan. At a large scale, the same row may leave too little room for a meaningful label. A vertical action arrangement is often clearer and more durable than ellipsizing essential controls.
@Composable
fun DownloadPrompt(
onDownload: () -> Unit,
onNotNow: () -> Unit,
) {
Card {
Column(
modifier = Modifier
.fillMaxWidth()
.padding(20.dp),
verticalArrangement = Arrangement.spacedBy(12.dp),
) {
Text(
text = "Download for offline reading",
style = MaterialTheme.typography.titleLarge,
)
Text(
text = "Keep this article available when you do not have a connection.",
style = MaterialTheme.typography.bodyLarge,
)
Button(
onClick = onDownload,
modifier = Modifier.fillMaxWidth(),
) {
Text("Download article")
}
TextButton(
onClick = onNotNow,
modifier = Modifier.fillMaxWidth(),
) {
Text("Not now")
}
}
}
}This is intentionally vertical at every scale. It uses a little more height on a compact screen, but avoids a mode switch that could hide a critical action only at large text. For a denser screen that truly needs different arrangements, choose from available width and test both layouts. Do not branch solely on fontScale; a font-scale threshold says little about the actual width left after localization, split screen, or display-size changes.
Avoid “fixes” that discard information
These shortcuts may make a screenshot look neat while making the product harder to use:
| Avoid | Prefer |
|---|---|
maxLines = 1 and ellipsis for a required title | Let it wrap; shorten optional copy only after product review. |
| A fixed card or row height | Content-driven height with vertical padding. |
| A tiny label beside an icon-only action | A visible label that can wrap, or an accessible icon button when the icon is genuinely unambiguous. |
Scaling text down to fit with TextUnit math | A different arrangement, less copy, or a separate detail screen. |
| A horizontally scrolling reading surface | Reflowed content that stays readable without side-to-side panning. |
softWrap = false, clipping, and forced single-line text are valid for a few constrained concepts—such as a short status chip or a code token—but they need a product reason and a large-font review. They should not be the default for titles, messages, form labels, errors, buttons, or settings.
Keep controls usable when labels grow
Text scaling affects more than paragraphs. Review every label that identifies or explains an action:
- Put labels above or below text fields when a trailing label would crowd input.
- Let error and helper messages increase a field’s height instead of drawing over the next field.
- Use a full-width or vertically stacked action when a button label is long.
- Keep interactive targets at least 48dp even when nearby text becomes taller.
- Check selected navigation labels, tabs, chips, dialogs, and bottom-sheet actions at the largest scale.
Material components provide sensible defaults, but their container still needs room. A bottom navigation bar with several long labels, for example, may be the wrong structure for a narrow window and large text together. Treat that as an adaptive design decision, not a typography bug. Adaptive NavigationSuiteScaffold for Phone and Tablet shows how navigation presentation can respond to available window space.
Preview large scales early
Compose Preview has a fontScale parameter, so a screen can be reviewed before launching an emulator. A focused 200% preview is a useful baseline because it matches Android 14’s maximum font-size setting.
import androidx.compose.runtime.Composable
import androidx.compose.ui.tooling.preview.Preview
@Preview(
name = "Large font 200%",
fontScale = 2f,
widthDp = 360,
showBackground = true,
)
@Composable
private fun SavedArticleRowLargeFontPreview() {
MaterialTheme {
SavedArticleRow(
article = sampleArticle,
onOpen = {},
onMore = {},
)
}
}The Preview API reference documents fontScale, and PreviewFontScale provides a seven-size multi-preview in current Compose tooling. Preview is fast feedback, not final proof: use realistic long names, localized strings, error states, and actual navigation chrome in addition to a convenient sample.
Test on a device at the maximum setting
Run the app with the system font size at its maximum, then navigate the complete flow. On Android 14 and later, the setting is under Settings → Accessibility → Display size and text → Font size; Android’s font-scaling guidance gives the same 200% test recommendation.
Check more than whether text is visible:
- Scroll through every screen and confirm no text, field, dialog action, or selected state is clipped.
- Rotate, resize, and test split screen if the app supports them.
- Enter validation errors and use long localized content, not only short English placeholders.
- Use TalkBack and keyboard/D-pad navigation to verify that expanded rows still have logical focus order.
- Check touch targets after the layout grows; large text must not push one small icon onto another action’s hit area.
This gives a real user path through the scaled screen. For the interaction and screen-reader details, pair this with Touch Targets, Focus Order, and TalkBack Support.
Common mistakes
Treating fontScale as a multiplier
Modern Android uses non-linear scaling at large values. Let sp text follow the system and make room in the layout; do not recreate the curve with arithmetic.
Using dp for line height
When font size grows but line height does not, multi-line text becomes visually cramped. Use sp or a relative em line height for text styles.
Testing only one short English string
Short labels conceal the problems introduced by translation, user data, error messages, and multi-line titles. Include long realistic content in previews and device checks.
Protecting the layout by hiding the content
Ellipsis, clipping, and fixed heights can preserve a design’s silhouette while removing the words people need to act. Reflow the layout or reduce nonessential content instead.
Large-font checklist
- Use
spfor text andsporemfor line height. - Consume named theme typography instead of scattered hard-coded text sizes.
- Keep text containers flexible; avoid fixed heights and required one-line content.
- Let actions reflow vertically when horizontal labels become crowded.
- Preserve 48dp interaction targets and distinct accessible actions.
- Preview a 200% font scale, then test the actual screen at the device maximum.
- Recheck rotation, split screen, long content, errors, TalkBack, and keyboard navigation.
Supporting large font sizes is a commitment to preserve meaning, not merely to enlarge glyphs. When the hierarchy, actions, and reading flow survive the largest setting, the screen is more resilient for every user and every future translation.