Compose Layouts, Modifiers & Lists
Reviewed & published by Brayan K
Real screens need structure: spacing, alignment, overlap, and scrolling lists. In Compose you achieve all of it with layout composables and modifiers.
Part of the free Kotlin course at LearnCodingFast — hands-on lessons with worked examples and the output they print, plus practice exercises and a quick quiz.
By the end you'll chain Modifiers, arrange children in Box, Column and Row, and render efficient scrolling lists with LazyColumn.
What You'll Learn in This Lesson
- Chaining modifiers (and why order matters)
- padding, background, size, fillMaxWidth
- Box for overlapping children
- Arrangement and alignment
- LazyColumn and LazyRow
- Emitting items with the items() DSL
1️⃣ Modifiers and Order
A Modifier decorates a composable — size, padding, background, click behavior. You chain calls, and they apply in order from left to right.
import androidx.compose.foundation.background
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.padding
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.unit.dp
@Composable
fun Banner() {
// Modifiers chain. They apply in ORDER, left to right.
Text(
text = "Welcome",
modifier = Modifier
.fillMaxWidth() // 1) take the full width
.background(Color.Cyan) // 2) paint the background
.padding(16.dp) // 3) inset the text by 16dp
)
}Swap two modifiers and the visual result changes — a classic source of "why is my padding the wrong color?" confusion.
2️⃣ Box: Overlapping Children
Box stacks children on top of each other along the z-axis. You position each child with Modifier.align(...).
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.size
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.unit.dp
@Composable
fun Badge() {
// Box overlaps children. Align each within the Box.
Box(modifier = Modifier.size(100.dp)) {
Text("Background", modifier = Modifier.align(Alignment.TopStart))
Text("Centered", modifier = Modifier.align(Alignment.Center))
Text("3", modifier = Modifier.align(Alignment.BottomEnd))
}
}For Column and Row, use verticalArrangement/horizontalArrangement for the main axis and the matching alignment for the cross axis.
3️⃣ Efficient Lists with LazyColumn
A plain Column composes every child eagerly. For long lists, LazyColumn composes only what's visible and uses the items() DSL.
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.lazy.LazyColumn
import androidx.compose.foundation.lazy.items
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
import androidx.compose.ui.unit.dp
@Composable
fun NameList() {
val names = listOf("Ada", "Grace", "Linus", "Margaret")
// LazyColumn only composes visible items — efficient for long lists.
LazyColumn(verticalArrangement = Arrangement.spacedBy(8.dp)) {
items(names) { name ->
Text(text = name, modifier = Modifier.padding(8.dp))
}
}
}Your turn. Replace the TODO, then run and compare.
// 🎯 YOUR TURN — replace the TODO then run.
import androidx.compose.foundation.lazy.LazyColumn
import androidx.compose.foundation.lazy.items
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
@Composable
fun NumberList() {
val numbers = listOf(1, 2, 3, 4, 5)
LazyColumn {
// 1) Emit one Text per number.
// 👉 replace TODO with: items(numbers) { n -> Text("Item $n") }
TODO
}
}Mini-Challenge: A Centered Card
Combine a Box, alignment, and a Column to center content on screen.
// 🎯 MINI-CHALLENGE: a centered card.
//
// 1. Write a @Composable named CenteredCard().
// 2. Use a Box with Modifier.fillMaxSize().
// 3. Inside, place a Column aligned to Alignment.Center
// (use the Box content lambda's align modifier).
// 4. In the Column, show two Text lines: a title and a subtitle.
//
// ✅ Expected: title and subtitle stacked, centered on screen.
@Composable
fun CenteredCard() {
// your code here
}Common Errors (and the fix)
- Wrong modifier order — padding then background differs from the reverse. Order the chain to match the look you want.
- Looping in LazyColumn — Don't write a normal for loop; use the items() DSL so it stays lazy.
- Confusing arrangement and alignment — Arrangement controls the main axis; alignment controls the cross axis.
- Using Column for huge lists — It composes everything eagerly. Reach for LazyColumn.
Pro Tips
- 💡 Pass a Modifier param: reusable composables should accept modifier: Modifier = Modifier so callers can style them.
- 💡 Spacing via Arrangement: prefer Arrangement.spacedBy(8.dp) over padding hacks.
- 💡 Provide a key in items() for stable, efficient list updates.
📋 Quick Reference — Layouts
| Concept | Compose API |
|---|---|
| Chain modifiers | Modifier.padding(8.dp).fillMaxWidth() |
| Overlap children | Box { ... } |
| Spacing (main axis) | Arrangement.spacedBy(8.dp) |
| Cross-axis align | Alignment.CenterVertically |
| Lazy list | LazyColumn { items(list) { } } |
🎉 Lesson Complete!
- ✅ Modifiers chain and apply in order
- ✅ Box overlaps; Column/Row stack
- ✅ Arrangement = main axis, alignment = cross axis
- ✅ LazyColumn renders long lists efficiently
- ✅ items(list) { } emits children lazily
Practice quiz
What is a Modifier used for in Compose?
- To declare a new composable
- To change a composable's size, padding, behavior, and appearance
- To import packages
- To define state
Answer: To change a composable's size, padding, behavior, and appearance. A Modifier decorates a composable: layout, drawing, padding, click behavior, and more.
How are modifiers combined?
- By chaining calls: Modifier.padding(8.dp).fillMaxWidth()
- By a comma-separated list
- By the '+' operator only
- They cannot be combined
Answer: By chaining calls: Modifier.padding(8.dp).fillMaxWidth(). Modifiers chain fluently; each call returns a new Modifier.
Why does modifier order matter?
- It never matters
- Only for color modifiers
- Modifiers are applied in sequence, so padding-then-background differs from background-then-padding
- Alphabetical order is required
Answer: Modifiers are applied in sequence, so padding-then-background differs from background-then-padding. Modifiers apply left to right, so order changes the visual result.
Which composable overlaps its children on top of each other?
- Column
- Row
- Stack
- Box
Answer: Box. Box stacks children along the z-axis, overlapping them; you align each within it.
Which composable lazily renders a vertically scrolling list?
- LazyColumn
- Column
- ScrollView
- RecyclerColumn
Answer: LazyColumn. LazyColumn only composes the items currently visible, like RecyclerView.
Inside a LazyColumn, how do you emit many items from a list?
- for (x in list) Item(x)
- forEach { Item(it) }
- items(list) { Item(it) }
- repeat(list.size)
Answer: items(list) { Item(it) }. The items(list) { } DSL function emits a child for each element lazily.
Which parameter controls spacing along a Column's main (vertical) axis?
- horizontalAlignment
- contentScale
- verticalArrangement
- padding only
Answer: verticalArrangement. verticalArrangement (e.g. Arrangement.spacedBy) positions children along a Column's main axis.
In a Row, which parameter aligns children on the cross (vertical) axis?
- verticalAlignment
- horizontalArrangement
- verticalArrangement
- contentAlignment
Answer: verticalAlignment. A Row's cross axis is vertical, so verticalAlignment (e.g. Alignment.CenterVertically) applies.
Which modifier makes a composable as wide as its parent allows?
- Modifier.wrapContent()
- Modifier.matchParent()
- Modifier.fillMaxWidth()
- Modifier.expand()
Answer: Modifier.fillMaxWidth(). Modifier.fillMaxWidth() stretches the composable to the parent's full width.
Why prefer LazyColumn over a Column for a long list?
- LazyColumn centers text automatically
- Column cannot hold more than 10 items
- LazyColumn requires no imports
- LazyColumn only composes visible items, so it is efficient for large lists
Answer: LazyColumn only composes visible items, so it is efficient for large lists. A plain Column composes every child eagerly; LazyColumn composes lazily as you scroll.
Continue this course
- Previous: State & Recomposition in Compose
- Next: Navigation in Compose — NavController, NavHost, routes and passing arguments
- Quick reference: Kotlin cheat sheet
Frequently asked questions
What is a Modifier, really?
A Modifier is an ordered, immutable chain of decorations you attach to a composable. Each call — padding, background, size, clickable, fillMaxWidth — returns a new Modifier, and they're applied in sequence. Modifiers handle layout, drawing, gestures, and semantics, so most of the 'how does this look and behave' detail lives in the modifier rather than the composable itself.
Why does modifier order matter so much?
Because modifiers apply left to right, each one wrapping the next. Modifier.padding(8.dp).background(Color.Red) pads first, then paints the background only around the inner content, leaving an unpainted margin. Reverse them — background(Color.Red).padding(8.dp) — and the background fills the outer area while the padding pushes content inward. Same modifiers, different visual result.
When do I use Box vs Column vs Row?
Use Column to stack children vertically, Row to lay them out horizontally, and Box to overlap children on the z-axis (for example a badge over an avatar, or text over an image). Box is also handy as a single-child container when you just need to apply alignment or a background. You nest these freely to build complex screens.
How is LazyColumn different from a regular Column?
A Column composes all of its children eagerly, which is fine for a handful of items but wasteful for long or unbounded lists. LazyColumn (and LazyRow) only composes the items currently visible, recycling as you scroll — the Compose equivalent of RecyclerView. You emit children through its DSL, typically items(list) { item -> ... }, rather than a normal loop.