Creating Self-Adjusting TextFields with Jetpack Compose
Ali Rezaiyan2 min read

I needed a text field that stays readable no matter how much the user types: instead of clipping the text or scrolling, the font should get a bit smaller until it fits. Here's how I did it in Jetpack Compose.
If you're also working on Compose animations, the official codelab is a good place to start.
Short version
- Compose makes it easy to react to the space a layout gets.
ParagraphIntrinsicstells you how wide a piece of text will be in a given style.- A shrinking text field works well for chat input, forms, and search bars.
The problem
Long input in chat bubbles and forms tends to get cut off or pushed behind a scroll bar. I wanted a text field that shrinks the font just enough to fit the width it has.
What I used
BoxWithConstraints, to know how wide the parent is.ParagraphIntrinsics, to measure how wide the text wants to be.LocalDensity, to convert between dp and px.
The code
@Composable
fun ResizableInputField(
text: String,
onValueChange: (String) -> Unit,
) {
BoxWithConstraints {
val shrunkFontSize = calculateShrunkFontSize(text)
BasicTextField(
value = text,
onValueChange = onValueChange,
textStyle = TextStyle(fontSize = shrunkFontSize),
)
}
}
@Composable
private fun BoxWithConstraintsScope.calculateShrunkFontSize(text: String): TextUnit {
var shrunkFontSize = Theme.typography.displayMedium.fontSize
val calculateIntrinsics: @Composable () -> ParagraphIntrinsics = {
ParagraphIntrinsics(
text = text,
style = Theme.typography.displayMedium.copy(fontSize = shrunkFontSize),
density = LocalDensity.current,
fontFamilyResolver = createFontFamilyResolver(LocalContext.current),
)
}
var intrinsics = calculateIntrinsics()
with(LocalDensity.current) {
while (intrinsics.maxIntrinsicWidth > maxWidth.toPx()) {
shrunkFontSize *= 0.9f
intrinsics = calculateIntrinsics()
}
}
return shrunkFontSize
}
It starts at the normal font size, measures the text, and keeps shrinking it by 10% until it fits the available width. You can use a regular TextField instead of BasicTextField and it works the same way.
Why bother
On small screens this keeps long input readable, without a scroll bar or text that gets cut off.
Wrapping up
This only needed a few Compose building blocks and no extra libraries. It's a small detail, but it makes the input feel a lot more finished.
I'm on GitHub (@rezaiyan) and Twitter (@arezaiyan) if you want to see more Compose experiments.