Implement pinch-to-zoom gestures to support scalable content in your app. This is the standard, platform-consistent method for improving accessibility, allowing users to intuitively adjust the size of text and UI elements to fit their needs. Your app can define custom scaling behavior with granular control and contextual behavior that offers an experience that users often discover more quickly than a system-level feature like screen magnification.
Choose a scaling strategy
The strategies covered in this guide cause the UI to reflow and reorganize to fit the screen's width. This provides a significant accessibility benefit by eliminating the need for horizontal panning and the frustrating "zig-zag" motion that would otherwise be required to read long lines of text.
Further Reading: Research confirms that for users with low vision, reflowing content is significantly more readable and easier to navigate than interfaces that require two-dimensional panning. For more details, see A Comparison of Pan-and-Scan and Reflowable Content on Mobile Devices.
Scale either all elements or only text elements
The following table demonstrates the visual effect of each scaling strategy.
| Strategy | Density scaling | Font scaling |
|---|---|---|
Behavior |
Scales everything proportionally. The content reflows to fit its container, so the user doesn't need to pan horizontally to see all content. |
Only affects text elements. The overall layout and non-text components stay the same size. |
What Scales |
All visual elements: Text, components (buttons, icons), images, and layout spacing (padding, margins) |
Text only |
Demonstration |
Recommendations
Now that you've seen the visual differences, the following table helps you weigh the trade-offs and choose the best strategy for your content.
UI type |
Recommended strategy |
Reasoning |
Reading-intensive layouts Examples: News articles, messaging apps |
Density or font scaling |
Density scaling is preferred to scale the entire content area, including inline images. Font scaling is a straightforward alternative if only text needs to be scaled. |
Visually structured layouts Examples: App stores, social media feeds |
Density scaling |
Preserves the visual relationships between images and text in carousels or grids. The reflowing nature avoids horizontal panning, which would conflict with nested scrolling elements. |
Detect scaling gestures in Jetpack Compose
To support user-scalable content, you must first detect multi-touch gestures. In
Jetpack Compose, you can do this using the Modifier.transformable.
The transformable modifier is a high-level API that provides the zoomChange
delta since the last gesture event. This simplifies the state update logic to
direct accumulation (for example, scale *= zoomChange), making it ideal for
the adaptive scaling strategies covered in this guide.
Example implementations
The following examples show how to implement the density scaling and font scaling strategies.
Density scaling
This approach scales the base density of a UI area. As a result, all
layout-based measurements—including padding, spacing, and component sizes—are
scaled, as if the screen size or resolution had changed. Because text size
also relies on density, it also scales proportionally. This strategy is
effective when you want to uniformly enlarge all elements within a specific
area, maintaining the overall visual rhythm and proportions of your UI.
private class DensityScalingState( // Note: For accessibility, typical min/max values are ~0.75x and ~3.5x. private val minScale: Float = 0.75f, private val maxScale: Float = 3.5f, private val currentDensity: Density ) { val transformableState = TransformableState { zoomChange, _, _ -> scaleFactor.floatValue = (scaleFactor.floatValue * zoomChange).coerceIn(minScale, maxScale) } val scaleFactor = mutableFloatStateOf(1f) fun scaledDensity(): Density { return Density( currentDensity.density * scaleFactor.floatValue, currentDensity.fontScale ) } }
Font scaling
This strategy is more targeted, modifying only the fontScale factor. The
result is that only text elements grow or shrink, while all other layout
components—such as containers, padding, and icons—remain a fixed size. This
strategy is well-suited for improving text legibility in reading-intensive
apps.
class FontScaleState( // Note: For accessibility, typical min/max values are ~0.75x and ~3.5x. private val minScale: Float = 0.75f, private val maxScale: Float = 3.5f, private val currentDensity: Density ) { val transformableState = TransformableState { zoomChange, _, _ -> scaleFactor.floatValue = (scaleFactor.floatValue * zoomChange).coerceIn(minScale, maxScale) } val scaleFactor = mutableFloatStateOf(1f) fun scaledFont(): Density { return Density( currentDensity.density, currentDensity.fontScale * scaleFactor.floatValue ) } }
Shared demo UI
This is the shared DemoCard composable used by both of the preceding examples
to highlight the different scaling behaviors.
@Composable private fun DemoCard() { Card( modifier = Modifier .width(360.dp) .padding(16.dp), shape = RoundedCornerShape(12.dp) ) { Column( modifier = Modifier.padding(16.dp), verticalArrangement = Arrangement.spacedBy(16.dp) ) { Text("Demo Card", style = MaterialTheme.typography.headlineMedium) var isChecked by remember { mutableStateOf(true) } Row(verticalAlignment = Alignment.CenterVertically) { Text("Demo Switch", Modifier.weight(1f), style = MaterialTheme.typography.bodyLarge) Switch(checked = isChecked, onCheckedChange = { isChecked = it }) } Row(verticalAlignment = Alignment.CenterVertically) { Icon(Icons.Filled.Person, "Icon", Modifier.size(32.dp)) Spacer(Modifier.width(8.dp)) Text("Demo Icon", style = MaterialTheme.typography.bodyLarge) } Row( Modifier.fillMaxWidth(), horizontalArrangement = Arrangement.SpaceBetween ) { Box( Modifier .width(100.dp) .weight(1f) .height(80.dp) .background(Color.Blue) ) Box( Modifier .width(100.dp) .weight(1f) .height(80.dp) .background(Color.Red) ) } Text( "Demo Text: Lorem ipsum dolor sit amet, consectetur adipiscing elit," + " sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.", style = MaterialTheme.typography.bodyMedium, textAlign = TextAlign.Justify ) } } }
Tips and considerations
To create a more polished and accessible experience, consider the following recommendations:
- Consider offering non-gesture scale controls: Some users may have difficulty with gestures. To support these users, consider providing an alternative way to adjust or reset the scale that doesn't rely on gestures.
- Build for all scales: Test your UI against both in-app scaling and system-wide font or display settings. Check that your app's layouts adapt correctly without breaking, overlapping, or hiding content. Learn more about how to build adaptive layouts.