I started to write an introductory book on Jetpack Compose but couldn’t keep up with the rapid changes in the Compose tooling, so I am condensing and publishing the few chapters I have written for free. Here is what would have been chapter 2 of the book.
Today’s lesson is dedicated to understanding how to work with layouts work in Compose. Layouts are used to arrange your compostables, providing structure, anchor, and coordination for the composables that emit the UI. Compose layouts are comparable to XML-based views’ ViewGroup. Simply put, layouts define how views are positioned and arranged on the screen, and modifiers adjust the behavior or appearance of views. Without the proper layout, Composable functions’ output will stack on top of each other, making creating an intuitive user interface challenging.
Layout Basics and Modifiers in Jetpack Compose
Jetpack Compose comes with built-in layouts that you can use to create a UI for your app. These built-in layouts, such as Column, Row, and Box, make it easy to arrange views on the screen. You can also create custom layouts to suit your app’s specific needs. Custom layouts give you full control over how your views are positioned and arranged. This is particularly useful when creating complex UI elements that can’t be achieved with built-in layouts alone.
Modifiers are another essential aspect of Jetpack Compose. They allow you to customize the behavior and appearance of views in your app. For example, you can use modifiers to adjust the size and color of text, add padding around views, or even change the entire background color of your app. Modifiers can be combined with layouts to create complex UI elements that are both functional and aesthetically pleasing. Understanding how to use layouts and modifiers in Jetpack Compose is key to building great Android apps.
In this chapter, you will explore the fundamental Compose layouts, such as Column, Row, and Box, as well ConstraintLayout
, to get a better understanding of how each layout component arranges its children. You will learn that the Box layout stacks children over each other, while Column places children vertically, and Row positions children horizontally. We will create the UI of a mock login screen shown in Figure 3.1 to demonstrate the Compose layouts we will learn.

Introduction to Compose Modifiers
Modifiers allow us to configure a composable to control the size, padding, focus state, click handlers, etc. You can use modifiers for much more than sizing, including expanding a composable or applying an aspect ratio. You can chain modifiers to create richer composable. Order and scope are important with modifiers; when you apply multiple modifiers to a composable, they will be applied in the order specified, and the selected composable will determine the available modifiers. Modifiers are similar in concept to the view attributes in XML.

The good thing about modifiers is that it is a Kotlin objects. The Modifier object provides a wide selection of methods that can be called upon to configure properties such as borders, padding, background, size requirements, event handlers, and gestures, to name just a few. So there is no context switching when you modify a composable’s appearance or behavior. The selected composable will expose the Modifier methods that apply to it. You have already seen a few usages of modifiers and will continue to see more. It is the most commonly used Compose object.

Box Layout
The box could be likened to a Framelayout in a View system. The Children of the Box layout will be stacked over each other. You can use the align modifier to specify where the composable should be drawn. For example, look at the Box layout demo in Figure 3.3. Notice how the shapes stack on top of each other. You can move them around with modifiers; otherwise, they stack on each other.

The column demo above could be created with the code in Example 3.1
@Composable
fun ColumnDemo() {
Column(
modifier = Modifier.fillMaxSize(),
verticalArrangement = Arrangement.Center,
horizontalAlignment = Alignment.CenterHorizontally
) {
Image(
painter = painterResource(id = R.drawable.salad_bowl),
contentDescription = "Salad bowl image",
modifier = Modifier.padding(bottom = 16.dp)
)
Image(
painter = painterResource(id = R.drawable.cup_cake),
contentDescription = "Cup cake image",
modifier = Modifier.padding(bottom = 16.dp)
)
Text(
text = LoremIpsum(100).values.joinToString(" "),
textAlign = TextAlign.Center,
modifier = Modifier.padding(bottom = 16.dp)
)
Button(
onClick = { /*TODO*/ },
modifier = Modifier.padding(bottom = 16.dp)
) {
Text(text = "Subscribe")
}
}
}
Example 3.1 – Column DemoRow Layout
As the name suggests, the Row is composable and lays out its children horizontally on the screen. Rows are oriented from left to right. This is particularly useful when you want to create a layout with several components that must be displayed side-by-side. The child components are added to the row individually when using the Row layout, starting from the left. The layout then automatically adjusts the spacing between the children to ensure they fit correctly on the screen.
The Row layout can create custom views with a horizontal orientation, such as a toolbar with multiple icons or a horizontally scrolling list. For example, suppose you want to create a row with an icon and a label. You could use the Row layout to achieve this by adding the two components to the Row. The Row would then arrange the two components side-by-side, with the icon on the left and the label on the right. How many rows can you spot on the login screen in Figure 3.7?

Jetpack Compose ConstraintLayout
Jetpack Compose ConstraintLayout is a layout that allows you to position composables relative to other composables on the screen. While the previous layouts we looked at, Box, Row, and Column, enable you to position elements relative to the parent, ConstraintLayout allows you to position composables with reference to each other. Therefore, if you need to lay out composables in relation to other composables, you will consider using ConstraintLayout because it aligns its children with each other regardless of the screen size.
The concept of ConstraintLayout in Jetpack Compose works similarly to that in XML. You create a Compose ConstraintLayout using the constrainAs modifiers in contrast to XML ConstraintLayout, where each element requires an ID. In Compose ConstraintLayout, there is no concept of ID. Instead, you need to assign a named reference to each composable you want to participate in the constrained scope. For instance, if you’ve created a layout and you know that there will be two composables in this layout – a button and a logo – inside a ConstraintLayout scope, you could create two references named loginButton and logoImage as follows: val (loginButton, profileImage) = createRefs()
For example, if we have an app that requires us to display a user’s profile, as shown in Figure 3.8, what Compose layout can we use to construct this UI?

While we could achieve this small layout with Rows and Columns, the layout could begin to falter in different screen sizes because we need to apply fixed dimensions to achieve the layout. This is an example of where you will consider ConstraintLayout because the composables on the screen are expected to be aligned with each other as shown, regardless of the screen size. Code snippet 3.3 demonstrates how you can achieve this layout with ConstraintLayout.
@Composable
fun HomeProfile(
modifier: Modifier,
name: String,
title: String,
onMessageClicked: () -> Unit,
onCallClicked: () -> Unit,
) {
ConstraintLayout(modifier = modifier) {
val (content, avatar) = createRefs()
Column(
modifier = Modifier
.constrainAs(content) {
start.linkTo(parent.start)
end.linkTo(parent.end)
top.linkTo(avatar.top, margin = 45.dp)
width = Dimension.fillToConstraints
height = Dimension.wrapContent
}
.background(
color = Colors.CardBackground,
shape = RoundedCornerShape(24.dp)
)
.padding(32.dp),
horizontalAlignment = Alignment.CenterHorizontally
) {
TitleH2(
modifier = Modifier.padding(top = 20.dp),
text = name
)
BodyBold(
modifier = Modifier,
text = title
)
Spacer(modifier = Modifier.height(32.dp))
Row(
modifier = Modifier,
horizontalArrangement = Arrangement.spacedBy(16.dp)
) {
AppOutlinedButton(
modifier = Modifier
.weight(1f),
text = stringResource(id = R.string.message),
onClick = onMessageClicked
)
AppButton(
modifier = Modifier
.weight(1f),
text = stringResource(id = R.string.call),
onClick = onCallClicked
)
}
}
Image(
painter = painterResource(id = R.drawable.val_headshot_100),
contentDescription = "",
contentScale = ContentScale.Crop,
modifier = Modifier
.clip(CircleShape)
.constrainAs(avatar) {
start.linkTo(parent.start)
end.linkTo(parent.end)
top.linkTo(parent.top)
width = Dimension.value(90.dp)
height = Dimension.value(90.dp)
}
)
}
}
Example 3.2 – Sample profile UI codePutting it Together
We can now use our understanding of the compose layouts of Box, Column and Row to create the UI for the login screen shown in figure 3.1. From the figure 3.4, we know the layouts and views participating in this UI. We have discussed that the Box layout should be the root layout and most of the views will be contained in a column layout which itself is nested in the Box layout.
Code snipped 3.8 shows a redacted composable for this login UI screen. Notice the use of Row layout multiple times to nest other composables such as buttons and texts.
@Composable
fun LoginScreen() {
Box(modifier = Modifier.fillMaxSize())
{
Column(
horizontalAlignment = Alignment.CenterHorizontally)
{
Image(painter = painterResource(id = R.drawable.login_logo))
Text(text = stringResource(id = R.string.welcome_back))
Text(text = stringResource(id = R.string.sign_in_continue))
OutlinedTextField(
value = "[email protected]",
onValueChange = {})
OutlinedTextField(
value = "password",
onValueChange = {})
Row(verticalAlignment = Alignment.CenterVertically) {
Row(verticalAlignment = Alignment.CenterVertically) {
Checkbox(checked = true, onCheckedChange = {})
Text(text = stringResource(id = R.string.remember_me))
}
Text(text = stringResource(id = R.string.forgot_password))
}
Button(onClick = {}) {Text(text = stringResource(id = R.string.sign_in))}
Spacer(modifier = Modifier.height(12.dp))
Row(verticalAlignment = Alignment.CenterVertically) {
Text(text = stringResource(id = R.string.or_continue))
}
Row(verticalAlignment = Alignment.CenterVertically) {
Button( onClick = {}) {
Row(verticalAlignment = Alignment.CenterVertically) {
Icon(painter = painterResource(id = R.drawable.google_icon),
contentDescription = "Google icon")
Spacer(modifier = Modifier.width(12.dp))
Text(text = "Google")
}
}
Spacer(modifier = Modifier.width(16.dp))
Button( onClick = { },) {
Row( verticalAlignment = Alignment.CenterVertically) {
Icon( painter = painterResource(id = R.drawable.facebook_icon),
contentDescription = "Facebook icon")
Spacer(modifier = Modifier.width(12.dp))
Text( text = "Facebook")
}
}
}
Row() {
Text( text = stringResource(id = R.string.do_not_have_account))
Spacer(modifier = Modifier.width(5.dp))
Text(text = stringResource(id = R.string.sign_up))
}
}
}
}
Example 3.3 – Login UIA full implementation of this demo login screen can be found on this Github repo
Summary
Today’s lesson taught us about some of the most important Jetpack Compose layouts, such as Column, Row, Box, and ConstraintLayout. These layouts are the building blocks for creating beautiful and interactive Android user interfaces. Although we only scratched the surface of these layouts, you now have a solid understanding of their strengths and use cases. For example, the Column layout is ideal for positioning elements vertically, while the Row layout works best for horizontal positioning. Remember to pay close attention to the modifiers you apply to these layouts, as they can affect the positioning and appearance of child elements.
In an upcoming lesson, we will cover LazyColumn and LazyRow layouts, allowing you to create scrollable lists of elements vertically and horizontally. With these layouts, you can create more advanced and dynamic user interfaces that respond to user input and display large amounts of data clearly and organized. By mastering these layout techniques, you will be well on your way to becoming an expert in Jetpack Compose development.