Modifiers
Every composable we've written so far has been a plain, undecorated block sitting wherever Compose
puts it. Modifier is how you change that — it's a chainable set of instructions
that decorates or configures a composable: how big it is, how much space surrounds it, what color
sits behind it, whether it responds to taps, and much more. Almost every built-in composable
(Text, Column, Image, Button...) accepts a
modifier: Modifier parameter, and you should give your own RecipeBox composables the
same courtesy.
Modifier Is a Chain
Modifier starts as an empty, do-nothing object. You build up behavior by calling
functions on it, and each call returns a new Modifier that wraps the previous one —
so you chain them with dots, top to bottom:
@Composable
fun RecipeNameLabel(name: String) {
Text(
text = name,
modifier = Modifier
.padding(16.dp)
.fillMaxWidth()
.background(Color.LightGray)
.clickable { /* open recipe detail */ }
)
}
Read that chain top to bottom: start with nothing, add 16.dp of padding, then make
the result fill the available width, then paint a background behind it, then make the whole thing
clickable. Each step wraps the one before it — which is exactly why, as the next section shows,
the order you write these calls in is not cosmetic.
Order Matters: padding().background() vs background().padding()
This is the single most common Modifier surprise for people new to Compose. These two chains look almost identical, but they render completely differently:
Version A — padding, then background
@Composable
fun CategoryTagA() {
Text(
text = "Dessert",
color = Color.White,
modifier = Modifier
.padding(16.dp)
.background(Color(0xFFE91E63))
)
}
Version B — background, then padding
@Composable
fun CategoryTagB() {
Text(
text = "Dessert",
color = Color.White,
modifier = Modifier
.background(Color(0xFFE91E63))
.padding(16.dp)
)
}
| Chain | What actually happens |
|---|---|
.padding(16.dp).background(pink) |
The padding is reserved first, as empty transparent space around the element. The pink background is then drawn only inside that reserved space, hugging the text tightly. Visually: a small pink box with a transparent 16dp margin around it before the tag even starts. |
.background(pink).padding(16.dp) |
The pink background is drawn first, across the full element bounds. The padding is then applied on top of that, pushing the text inward — but the pink is already behind the padded area too. Visually: one solid pink box, with the text sitting 16dp in from its edges. |
padding before
background shrinks the space the background gets to paint. background
before padding paints the full space, and the padding only pushes the content, not the
paint.Sizing: fillMaxWidth() and fillMaxSize()
Two of the most-used size modifiers tell a composable to grow to fill the space its parent offers, rather than shrinking to just fit its content:
@Composable
fun RecipeListScreen() {
Column(
modifier = Modifier
.fillMaxSize() // take all available width AND height
.padding(16.dp)
) {
Text(
text = "My Recipes",
modifier = Modifier.fillMaxWidth() // take all available width only
)
// recipe list below...
}
}
fillMaxSize() is common on a screen's root Column/Box so it
claims the entire screen. fillMaxWidth() is common on individual rows inside a list —
for example so a RecipeBox recipe card stretches edge to edge instead of hugging the width of its
title text.
Accepting a Modifier Parameter in Your Own Composables
When you write your own composable, always accept a modifier: Modifier = Modifier
parameter and pass it down to the root element inside — this is a Compose convention, not a
requirement, but skipping it makes your composable much harder to reuse, because the caller has no
way to adjust its padding, size, or click behavior from outside:
@Composable
fun RecipeCard(
title: String,
prepMinutes: Int,
modifier: Modifier = Modifier
) {
Column(
modifier = modifier
.fillMaxWidth()
.padding(12.dp)
) {
Text(text = title, style = MaterialTheme.typography.titleMedium)
Text(text = "$prepMinutes min", style = MaterialTheme.typography.bodySmall)
}
}
// Caller can now customize layout without touching RecipeCard itself:
@Composable
fun FeaturedRecipeCard(title: String, prepMinutes: Int) {
RecipeCard(
title = title,
prepMinutes = prepMinutes,
modifier = Modifier
.padding(24.dp)
.background(Color(0xFFFFF3E0))
)
}
Notice the parameter defaults to a plain, empty Modifier — so callers who don't need
any customization can simply omit it. And notice that modifier is applied first in
the chain inside RecipeCard, before the fillMaxWidth() and
padding() that belong to RecipeCard itself — this lets the caller's
modifiers wrap the composable's own internal ones, which is exactly the convention Compose's own
built-in composables (like Text and Button) follow.