1. Introduction
Being a UI toolkit, Compose makes it easy to implement your app's designs. You describe how you want your UI to look, and Compose takes care of drawing it on screen. This codelab teaches you how to write Compose UIs. It assumes you understand the concepts taught in the basics codelab, so make sure that you complete that codelab first. In the Basics codelab, you learned how to implement simple layouts using Surfaces, Rows and Columns. You also augmented these layouts with modifiers like padding, fillMaxWidth, and size.
In this codelab you implement a more realistic and complex layout, learning about various out of the box composables and modifiers along the way. After finishing this codelab, you should be able to transform a basic app's design into working code.
This codelab does not add any actual behavior to the app. To learn about state and interaction instead, complete the State in Compose codelab instead.
For more support as you're walking through this codelab, check out the following code-along:
What you'll learn
In this codelab, you will learn:
- How modifiers help you augment your composables.
- How standard layout components like Column and LazyRow position child composables.
- How alignments and arrangements change the position of child composables in their parent.
- How Material composables like Scaffold and Bottom Navigation help you create comprehensive layouts.
- How to build flexible composables using slot APIs.
- How to build layouts for different screen configurations.
What you'll need
- Latest Android Studio.
- Experience with Kotlin syntax, including lambdas.
- Basic experience with Compose. If you haven't already, complete the Jetpack Compose basics codelab before starting this codelab.
- Basic knowledge of what a composable is, and what modifiers are.
What you'll build
In this codelab, you implement a realistic app design based on mocks provided by a designer. MySoothe is a well-being app that lists various ways to improve your body and mind. It contains a section that lists your favorite collections, and a section with physical exercises. This is what the app looks like:


2. Getting set up
In this step, you download code that contains theming and some basic setup.
Get the code
The code for this codelab can be found in the codelab-android-compose GitHub repository. To clone it, run:
$ git clone https://github.com/android/codelab-android-compose
Alternatively, you can download two zip files:
Check out the code
The downloaded code contains code for all available Compose codelabs. To complete this codelab, open the BasicLayoutsCodelab project inside Android Studio.
We recommend that you start with the code in the main branch and follow the codelab step by step at your own pace.
3. Start with a plan
We will start by implementing the portrait design of the app - let's take a closer look:
When you're asked to implement a design, a good way to start is by getting a clear understanding of its structure. Don't start coding straight away, but instead analyze the design itself. How can you split this UI into multiple reusable parts?
So let's give this a go with our design. At the highest abstraction level, we can break this design down into two pieces:
- The screen's content.
- The bottom navigation.

Drilling down, the screen content contains three sub-parts:
- The Search bar.
- A section called "Align your body".
- A section called "Favorite collections".

Inside each section, you can also see some lower level components that are re-used:
- The "align your body" element that's shown in a horizontally scrollable row.

- The "favorite collection" card that's shown in a horizontally scrollable grid.

Now that you've analyzed the design, you can start implementing composables for every identified piece of the UI. Start with the lowest level composables and continue to combine these into more complex ones. By the end of the codelab, your new app will look like the provided design.
4. Search bar - Modifiers
The first element to transform into a composable is the Search bar. Let's take another look at the design:
Based on this screenshot alone, it would be quite difficult to implement this design in a pixel-perfect way. Generally, a designer conveys more information about the design. They can give you access to their design tool, or share so-called redlining designs. In this case, our designer handed off the redlining designs, which you can use to read off any sizing values. The design is shown with an 8dp grid overlay, so you can easily see how much space is between and around elements. Additionally, some spacings are added explicitly to clarify certain sizes.

You can see that the search bar should have a height of 56 density-independent pixels. It should also fill the full width of its parent.
To implement the search bar, use a Material component called Text field. The Compose Material library contains a composable called TextField, which is the implementation of this Material component.
Start with a basic TextField implementation. In your code base, open MainActivity.kt and search for the SearchBar composable.
Inside the composable called SearchBar, write the basic TextField implementation:
import androidx.compose.material3.TextField
@Composable
fun SearchBar(
modifier: Modifier = Modifier
) {
TextField(
value = "",
onValueChange = {},
modifier = modifier
)
}
Some points to notice:
- You hardcoded the text field's value, and the
onValueChangecallback doesn't do anything. Since this is a layout-focused codelab, you ignore anything that has to do with state.
- The
SearchBarcomposable function accepts amodifierparameter and passes this on to theTextField. This is a best practice as per Compose guidelines. This allows the method's caller to modify the composable's look & feel, which makes it more flexible and reusable. You'll continue this best practice for all composables in this codelab.
Let's look at the preview of this composable. Remember that you can use the Preview functionality in Android Studio to quickly iterate on your individual composables. MainActivity.kt contains previews for all the composables you'll build in this codelab. In this case, the method SearchBarPreview renders our SearchBar composable, with some background and padding to give it a bit more context. With the implementation you just added, it should look like this:

There are some things missing. First, let's fix the size of the composable using modifiers.
When writing composables, you use modifiers to:
- Change the composable's size, layout, behavior, and appearance.
- Add information, like accessibility labels.
- Process user input.
- Add high-level interactions, like making an element clickable, scrollable, draggable, or zoomable.
Each composable that you call has a modifier parameter that you can set to adapt that composable's look, feel and behavior. When you set the modifier, you can chain multiple modifier methods to create a more complex adaptation.
In this case, the search bar should be at least 56dp high, and fill its parent's width. To find the right modifiers for this, you can go through the list of modifiers and look at the Size section. For the height, you can use the heightIn modifier. This makes sure that the composable has a specific minimum height. It can, however, become larger when, for example, the user enlarges their system font size. For the width you can use the fillMaxWidth modifier. This modifier makes sure that the search bar uses up all the horizontal space of its parent.
Update the modifier to match the code below:
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.heightIn
import androidx.compose.material3.TextField
@Composable
fun SearchBar(
modifier: Modifier = Modifier
) {
TextField(
value = "",
onValueChange = {},
modifier = modifier
.fillMaxWidth()
.heightIn(min = 56.dp)
)
}
In this case, because one modifier influences the width, and the other the height, the order of these modifiers doesn't matter.
You also have to set some parameters of the TextField. Try to make the composable look like the design by setting the parameter values. Here's the design again as a reference:

These are the steps that you should take to update your implementation:
- Add the search icon.
TextFieldcontains a parameterleadingIconthat accepts another composable. Inside, you can set anIcon, which in our case should be theSearchicon. Make sure to use the right ComposeIconimport. - You can use the
TextFieldDefaults.colorsto override specific colors. Set thefocusedContainerColorand theunfocusedContainerColorof the text field to MaterialTheme'ssurfacecolor. - Add a placeholder text "Search" (you can find this as string resource
R.string.placeholder_search).
When you're done, your composable should look similar to this:
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.heightIn
import androidx.compose.ui.res.stringResource
import androidx.compose.material3.Icon
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Text
import androidx.compose.material3.TextField
import androidx.compose.material3.TextFieldDefaults
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.Search
@Composable
fun SearchBar(
modifier: Modifier = Modifier
) {
TextField(
value = "",
onValueChange = {},
leadingIcon = {
Icon(
imageVector = Icons.Default.Search,
contentDescription = null
)
},
colors = TextFieldDefaults.colors(
unfocusedContainerColor = MaterialTheme.colorScheme.surface,
focusedContainerColor = MaterialTheme.colorScheme.surface
),
placeholder = {
Text(stringResource(R.string.placeholder_search))
},
modifier = modifier
.fillMaxWidth()
.heightIn(min = 56.dp)
)
}
Notice that:
- You added a
leadingIconshowing the search icon. This icon does not need a content description, as the text field's placeholder already describes the meaning of the text field. Remember that a content description is normally used for accessibility purposes and gives the user of your app a textual representation of an image or icon.
- To adapt the background color of the text field, you set the
colorsproperty. Instead of a separate parameter for each color, the composable contains one combined parameter. Here you pass in a copy of theTextFieldDefaultsdata class, where you update only the colors that are different. In this case, that's only theunfocusedContainerColorandfocusedContainerColorcolor.
In this step you saw how you can use composable parameters and modifiers to change a composable's look and feel. This applies to both composables provided by the Compose and Material libraries, and to the ones you write yourself. You should always think about providing parameters to customize the composable you're writing. You should also add a modifier property so the composable's look and feel can be adapted from the outside.
5. Align your body - Alignment
The next composable you'll implement is the "Align your body" element. Let's take a look at its design, including the redlines design next to it:


The redlines design now also contains baseline-oriented spacings. Here's the information we get from it:
- The image should be 88dp high.
- The spacing between the baseline of the text and the image should be 24dp.
- The spacing between the baseline and the bottom of the element should be 8dp.
- The text should have a typography style of bodyMedium.
To implement this composable, you need an Image and a Text composable. They need to be included in a Column, so they are positioned underneath each other.
Find the AlignYourBodyElement composable in your code and update its content with this basic implementation:
import androidx.compose.foundation.Image
import androidx.compose.foundation.layout.Column
import androidx.compose.ui.res.painterResource
@Composable
fun AlignYourBodyElement(
modifier: Modifier = Modifier
) {
Column(
modifier = modifier
) {
Image(
painter = painterResource(R.drawable.ab1_inversions),
contentDescription = null
)
Text(text = stringResource(R.string.ab1_inversions))
}
}
Notice that:
- You set the
contentDescriptionof the image to null, as this image is purely decorative. The text below the image describes enough of the meaning, so the image does not need an extra description. - You are using a hard-coded image and text. In the next step, you'll move these to use parameters provided in the
AlignYourBodyElementcomposable to make them dynamic.
Take a look at the preview of this composable:

There are some improvements to be made. Most noticeably, the image is too large and not shaped as a circle. You can adapt the Image composable with the size and clip modifiers and the contentScale parameter.
The size modifier adapts the composable to fit a certain size, similar to the fillMaxWidth and heightIn modifiers that you saw in the previous step. The clip modifier works differently and adapts the composable's appearance. You can set it to any Shape and it clips the composable's content to that shape.
import androidx.compose.foundation.layout.size
import androidx.compose.foundation.shape.CircleShape
import androidx.compose.ui.draw.clip
@Composable
fun AlignYourBodyElement(
modifier: Modifier = Modifier
) {
Column(
modifier = modifier
) {
Image(
painter = painterResource(R.drawable.ab1_inversions),
contentDescription = null,
modifier = Modifier
.size(88.dp)
.clip(CircleShape)
)
Text(text = stringResource(R.string.ab1_inversions))
}
}
Currently your design in the Preview looks like this:

The image also needs to be scaled correctly. To do so, we can use the Image's contentScale parameter. There are several options, most notably:

In this case, the crop type is the correct one to use. After applying the modifiers and the parameter, your code should look like this:
import androidx.compose.foundation.layout.size
import androidx.compose.foundation.shape.CircleShape
import androidx.compose.ui.draw.clip
import androidx.compose.ui.layout.ContentScale
@Composable
fun AlignYourBodyElement(
modifier: Modifier = Modifier
) {
Column(
modifier = modifier
) {
Image(
painter = painterResource(R.drawable.ab1_inversions),
contentDescription = null,
contentScale = ContentScale.Crop,
modifier = Modifier
.size(88.dp)
.clip(CircleShape)
)
Text( text = stringResource(R.string.ab1_inversions) )
}
}
Your design should now look like this:

As a next step, align the text horizontally by setting the alignment of the Column.
In general, to align composables inside a parent container, you set the alignment of that parent container. So instead of telling the child to position itself in its parent, you tell the parent how to align its children.
For a Column, you decide how its children should be aligned horizontally. The options are:
- Start
- CenterHorizontally
- End
For a Row, you set the vertical alignment. The options are similar to those of the Column:
- Top
- CenterVertically
- Bottom
For a Box, you combine both horizontal and vertical alignment. The options are:
- TopStart
- TopCenter
- TopEnd
- CenterStart
- Center
- CenterEnd
- BottomStart
- BottomCenter
- BottomEnd
All of the container's children will follow this same alignment pattern. You can override the behavior of a single child by adding an align modifier to it.
For this design, the text should be centered horizontally. To do that, set the Column's horizontalAlignment to center horizontally:
import androidx.compose.ui.Alignment
@Composable
fun AlignYourBodyElement(
modifier: Modifier = Modifier
) {
Column(
horizontalAlignment = Alignment.CenterHorizontally,
modifier = modifier
) {
Image(
//..
)
Text(
//..
)
}
}
With these parts implemented, there are only some minor changes that you need to make the composable identical to the design. Try to implement these by yourself or reference the final code if you get stuck. Think of the following steps:
- Make the image and text dynamic. Pass them as arguments to the composable function. Don't forget to update the corresponding Preview and pass in some hard-coded data.
- Update the text to use the bodyMedium typography style.
- Update the baseline spacings of the text element per the diagram.

When you're done implementing these steps, your code should look similar to this:
import androidx.compose.foundation.layout.paddingFromBaseline
import androidx.compose.ui.Alignment
import androidx.compose.ui.layout.ContentScale
@Composable
fun AlignYourBodyElement(
@DrawableRes drawable: Int,
@StringRes text: Int,
modifier: Modifier = Modifier
) {
Column(
modifier = modifier,
horizontalAlignment = Alignment.CenterHorizontally
) {
Image(
painter = painterResource(drawable),
contentDescription = null,
contentScale = ContentScale.Crop,
modifier = Modifier
.size(88.dp)
.clip(CircleShape)
)
Text(
text = stringResource(text),
modifier = Modifier.paddingFromBaseline(top = 24.dp, bottom = 8.dp),
style = MaterialTheme.typography.bodyMedium
)
}
}
@Preview(showBackground = true, backgroundColor = 0xFFF5F0EE)
@Composable
fun AlignYourBodyElementPreview() {
MySootheTheme {
AlignYourBodyElement(
text = R.string.ab1_inversions,
drawable = R.drawable.ab1_inversions,
modifier = Modifier.padding(8.dp)
)
}
}
Check out the AlignYourBodyElement in the Design tab.

6. Favorite collection card - Material Surface
The next composable to implement is in a way similar to the "Align the body" element. Here's the design, including the redlines:


In this case, the full size of the composable is provided. You can see that the text should be titleMedium.
This container uses surfaceVariant as its background color which is different from the background of the whole screen. It also has rounded corners. We specify these for the favorite collection card using Material's Surface composable.
You can adapt the Surface to your needs by setting its parameters and modifier. In this case, the surface should have rounded corners. You can use the shape parameter for this. Instead of setting the shape to a Shape as for the Image in the previous step, you'll use a value coming from our Material theme.
Let's see what this would look like:
import androidx.compose.foundation.layout.Row
import androidx.compose.material3.Surface
@Composable
fun FavoriteCollectionCard(
modifier: Modifier = Modifier
) {
Surface(
shape = MaterialTheme.shapes.medium,
modifier = modifier
) {
Row {
Image(
painter = painterResource(R.drawable.fc2_nature_meditations),
contentDescription = null
)
Text(text = stringResource(R.string.fc2_nature_meditations))
}
}
}
And let's see the Preview of this implementation:

Next, apply the lessons learned in the previous step.
- Set the width of the
Row, and align its children vertically. - Set the size of the image per the diagram and crop it in its container.

Try to implement these changes yourself before looking at the solution code!
Your code would now look something like this:
import androidx.compose.foundation.layout.width
@Composable
fun FavoriteCollectionCard(
modifier: Modifier = Modifier
) {
Surface(
shape = MaterialTheme.shapes.medium,
modifier = modifier
) {
Row(
verticalAlignment = Alignment.CenterVertically,
modifier = Modifier.width(255.dp)
) {
Image(
painter = painterResource(R.drawable.fc2_nature_meditations),
contentDescription = null,
contentScale = ContentScale.Crop,
modifier = Modifier.size(80.dp)
)
Text(
text = stringResource(R.string.fc2_nature_meditations)
)
}
}
}
The preview should now look like this:

To finish up this composable, implement the following steps:
- Make the image and text dynamic. Pass them in as arguments to the composable function.
- Update the color to surfaceVariant.
- Update the text to use the titleMedium typography style.
- Update the spacing between the image and the text.
Your end result should look similar to this:
@Composable
fun FavoriteCollectionCard(
@DrawableRes drawable: Int,
@StringRes text: Int,
modifier: Modifier = Modifier
) {
Surface(
shape = MaterialTheme.shapes.medium,
color = MaterialTheme.colorScheme.surfaceVariant,
modifier = modifier
) {
Row(
verticalAlignment = Alignment.CenterVertically,
modifier = Modifier.width(255.dp)
) {
Image(
painter = painterResource(drawable),
contentDescription = null,
contentScale = ContentScale.Crop,
modifier = Modifier.size(80.dp)
)
Text(
text = stringResource(text),
style = MaterialTheme.typography.titleMedium,
modifier = Modifier.padding(horizontal = 16.dp)
)
}
}
}
//..
@Preview(showBackground = true, backgroundColor = 0xFFF5F0EE)
@Composable
fun FavoriteCollectionCardPreview() {
MySootheTheme {
FavoriteCollectionCard(
text = R.string.fc2_nature_meditations,
drawable = R.drawable.fc2_nature_meditations,
modifier = Modifier.padding(8.dp)
)
}
}
Check out the Preview of the FavoriteCollectionCardPreview.

7. Align your body row - Arrangements
Now that you've created the basic composables that are shown
