Row vs Column vs Box in Jetpack Compose

Quick answer: Use
Rowfor side-by-side children,Columnfor vertically stacked children, andBoxwhen children overlap. Configure the container’s arrangement and alignment first; give an individual child a scopedalignmodifier only when it needs an exception.
Row, Column, and Box are the three layout building blocks you will use constantly in Jetpack Compose. They are deliberately small APIs, but choosing the right one makes a screen easier to read, adapt, and maintain.
The official Compose layout basics describe the distinction simply: Row places children horizontally, Column places them vertically, and Box stacks them. This guide turns that rule into practical decisions you can use in real UI.
Choose the layout by the visual relationship
| You want to show | Use | Common examples |
|---|---|---|
| Items beside one another | Row | icon and label, profile header, action buttons |
| Items one below another | Column | settings form, article card, screen sections |
| Items on top of one another | Box | image badge, loading layer, floating button over content |
Think in axes:
- A
Rowhas a horizontal main axis and a vertical cross axis. - A
Columnhas a vertical main axis and a horizontal cross axis. - A
Boxhas no flow axis for its children; it positions each child in the same available space, so children can overlap.
For a refresher on how these pieces are composed into UI, see what Jetpack Compose is.
Use Row for horizontal content
Row is the natural fit when reading order is left-to-right: an avatar next to profile information, or a title next to an action.
@Composable
fun ProfileHeader(
name: String,
handle: String,
onMoreClick: () -> Unit,
) {
Row(
modifier = Modifier.fillMaxWidth(),
verticalAlignment = Alignment.CenterVertically,
horizontalArrangement = Arrangement.spacedBy(12.dp),
) {
Box(
modifier = Modifier
.size(48.dp)
.clip(CircleShape)
.background(MaterialTheme.colorScheme.primary),
)
Column(modifier = Modifier.weight(1f)) {
Text(text = name, style = MaterialTheme.typography.titleMedium)
Text(text = handle, style = MaterialTheme.typography.bodyMedium)
}
IconButton(onClick = onMoreClick) {
Icon(Icons.Outlined.MoreVert, contentDescription = "More options")
}
}
}horizontalArrangement controls distribution along the Row’s main axis. verticalAlignment controls the cross axis. Here, the name block receives weight(1f), so it consumes the remaining horizontal space and pushes the action to the end.
weight is a scope-specific modifier: it works inside Row and Column, not anywhere in the UI tree. In a Row, it allocates width; in a Column, it allocates height. The Modifier documentation explains this parent-data behavior in more detail.
Use Column for vertical sections
Choose Column when a user should scan content from top to bottom. A settings section, login form, and empty state are all common examples.
@Composable
fun EmptyDownloads(
onBrowseClick: () -> Unit,
) {
Column(
modifier = Modifier
.fillMaxSize()
.padding(24.dp),
verticalArrangement = Arrangement.Center,
horizontalAlignment = Alignment.CenterHorizontally,
) {
Icon(
imageVector = Icons.Outlined.Download,
contentDescription = null,
modifier = Modifier.size(48.dp),
)
Spacer(Modifier.height(16.dp))
Text("No downloads yet", style = MaterialTheme.typography.titleLarge)
Spacer(Modifier.height(8.dp))
Text("Save an item to read it later.")
Spacer(Modifier.height(20.dp))
Button(onClick = onBrowseClick) {
Text("Browse items")
}
}
}For a Column, the names are reversed from Row: verticalArrangement controls the main axis and horizontalAlignment controls the cross axis. Use Arrangement.spacedBy(16.dp) when every adjacent item should have the same gap; it is often clearer than adding a Spacer after each child.
Use Box for layers and overlays
Box is for a different relationship: several elements occupy the same region. It is ideal for a badge on an image, a progress indicator above content, or a button anchored over a map.
@Composable
fun PhotoWithBadge(
photo: Painter,
unreadCount: Int,
) {
Box(modifier = Modifier.size(160.dp)) {
Image(
painter = photo,
contentDescription = "Trip photo",
contentScale = ContentScale.Crop,
modifier = Modifier.fillMaxSize(),
)
if (unreadCount > 0) {
Badge(
modifier = Modifier
.align(Alignment.TopEnd)
.padding(8.dp),
) {
Text(unreadCount.toString())
}
}
}
}Alignment.TopEnd is a BoxScope modifier, so it is available to direct children of a Box. You can also set a default position for all children with contentAlignment:
Box(
modifier = Modifier.size(120.dp),
contentAlignment = Alignment.Center,
) {
CircularProgressIndicator()
}matchParentSize() versus fillMaxSize() in a Box
These modifiers can look similar but serve different purposes. fillMaxSize() asks a child to fill the constraints given by its parent, which can make that child affect the size of the Box. matchParentSize() instead waits for the Box size established by its other children, then matches it.
Use matchParentSize() for a background, scrim, or selection layer that must follow content without deciding the container’s size:
Box {
Box(
Modifier
.matchParentSize()
.background(Color.Black.copy(alpha = 0.2f)),
)
Text("Content that defines the Box size", Modifier.padding(16.dp))
}This behavior is documented on the official BoxScope.matchParentSize reference.
Arrangement, alignment, and child exceptions
Use the container parameters when the rule applies to most children. Use a child’s scoped align modifier only for the exception.
Column(
horizontalAlignment = Alignment.CenterHorizontally,
verticalArrangement = Arrangement.spacedBy(12.dp),
) {
Text("Centered title")
Text("Centered description")
Button(
onClick = {},
modifier = Modifier.align(Alignment.End),
) {
Text("Continue")
}
}The title and description inherit the centered cross-axis alignment. The button deliberately opts into Alignment.End. This is easier to understand than placing every child in its own wrapper just to change its position.
Common layout mistakes
Using a Column for a long, scrollable feed
A regular Column creates its children as part of the composition. For a long or unbounded list, use a lazy layout so visible items are composed as needed. Start with the LazyColumn guide when building a feed or results list.
Reaching for nested boxes when a row or column expresses the intent
Nested layouts are normal in Compose, but the outermost layout should communicate the primary relationship. A profile header is usually a Row containing a Column, not a stack of Box containers with manually positioned content. Compose is designed to handle nested layouts efficiently; prefer clarity and measure real performance before optimizing a readable hierarchy away.
Using Spacer everywhere
Spacer is useful for one-off gaps and flexible empty space. When all children need the same gap, Arrangement.spacedBy() states the intent once and avoids repeated spacing code.
Forgetting to test layout variants
The correct arrangement at a normal font size can clip or crowd at a larger scale, in dark theme, or with longer translated text. Create small component previews for your Row, Column, and Box layouts; Compose Preview makes those variants quick to inspect.
A practical decision checklist
Before adding a layout container, ask:
- Do the children belong beside, below, or on top of each other?
- Which axis should distribute space?
- Does every child follow the same alignment rule?
- Is this a small static group or a long list that needs a lazy layout?
- Can the composable be previewed with short, long, and empty content?
The answers usually point directly to Row, Column, or Box. Start with the simplest container that expresses the visual relationship, then add modifiers only when they solve a real layout requirement. The next layer of control is understanding Compose modifiers, including how modifier order changes a component’s behavior.
FAQ
Can I put a Column inside a Row?
Yes. This is one of the most common Compose patterns: use the Row for the broad horizontal structure and a nested Column for related text or actions that should stack vertically.
Should I use Row or Box for an icon beside text?
Use Row. The icon and text are separate, side-by-side siblings. Use Box only when one element should overlay another, such as a notification dot on an icon.
Does weight() work in Box?
No. weight() belongs to RowScope and ColumnScope, where there is a main axis along which remaining space can be shared. Position children in a Box with contentAlignment or Modifier.align() instead.