Compose layout basics

Jetpack Compose makes it much easier to design and build your app's UI. This document explains some of the building blocks Compose provides to help you lay out your UI elements.

Goals of layouts in Compose

The Jetpack Compose implementation of the layout system has two main goals: being high-performant, and making it easy to write custom layouts. High performance is achieved in Compose by prohibiting measuring layout children more than once. If multiple measurements are needed, Compose has a special system in place, intrinsic measurements. You can read more about this feature in Intrinsic measurements in Compose layouts.

Basics of Composable functions

Composable functions are the basic building block of Compose. A composable function is a function emitting Unit that describes some part of your UI. The function takes some input and generates what's shown on the screen. For more information about composables, take a look at the Compose mental model documentation.

A Composable function might emit several UI elements. However, if you don't provide guidance on how they should be arranged, Compose might arrange the elements in a way you don't like. For example, this code generates two text elements:

@Composable
fun ArtistCard() {
    Text("Alfred Sisley")
    Text("3 minutes ago")
}

Without guidance on how you want them arranged, Compose stacks the text elements on top of each other, making them unreadable:

Two text elements drawn on top of each other, making the text unreadable

Compose provides a collection of ready-to-use layouts to help you arrange your UI elements, and makes it easy to define your own, more-specialized layouts.

Standard layout components

In many cases, you can just use Compose's standard layout elements.

Use Column to place items vertically on the screen.

@Composable
fun ArtistCard() {
    Column {
        Text("Alfred Sisley")
        Text("3 minutes ago")
    }
}

Two text elements arranged in a column layout, so the text is readable

Similarly, use Row to place items horizontally on the screen. Both Column and Row support configuring the alignment of the elements they contain.

@Composable
fun ArtistCard(artist: Artist) {
    Row(verticalAlignment = Alignment.CenterVertically) {
        Image(/*...*/)
        Column {
            Text(artist.name)
            Text(artist.lastSeenOnline)
        }
    }
}

Shows a more complex layout, with a small graphic next to a column of text elements

Use Box to put elements on top of another. Box also supports configuring specific alignment of the elements it contains.

@Composable
fun ArtistAvatar(artist: Artist) {
    Box {
        Image(/*...*/)
        Icon(/*...*/)
    }
}

Shows two elements stacked on one another

Often these building blocks are all you need. You can write your own composable function to combine these layouts into a more elaborate layout that suits your app.

Compares three simple layout composables: column, row, and box

To set children's position within a Row, set the horizontalArrangement and verticalAlignment arguments. For a Column, set the verticalArrangement and horizontalAlignment arguments:

@Composable
fun ArtistCard(artist: Artist) {
    Row(
        verticalAlignment = Alignment.CenterVertically,
        horizontalArrangement = Arrangement.End
    ) {
        Image(/*...*/)
        Column { /*...*/ }
    }
}

Items are aligned to the right

Modifiers

Modifiers allow you to decorate or augment a composable. Modifiers let you do these sorts of things:

  • 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

Modifiers are standard Kotlin objects. Create a modifier by calling one of the Modifier class functions. You can chain these functions together to compose them:

@Composable
fun ArtistCard(
    artist: Artist,
    onClick: () -> Unit
) {
    val padding = 16.dp
    Column(
        Modifier
            .clickable(onClick = onClick)
            .padding(padding)
            .fillMaxWidth()
    ) {
        Row(verticalAlignment = Alignment.CenterVertically) { /*...*/ }
        Spacer(Modifier.size(padding))
        Card(elevation = 4.dp) { /*...*/ }
    }
}

A still more complex layout, using modifiers to change how the graphics are arranged and which areas respond to user input

In the code above, notice different modifier functions used together.

  • clickable makes a composable react to user input and shows a ripple.
  • padding puts space around an element.
  • fillMaxWidth makes the composable fill the maximum width given to it from its parent.
  • size() specifies an element's preferred width and height.

Order of modifiers matters

The order of modifier functions is significant. Since each function makes changes to the Modifierreturned by the previous function, the sequence affects the final result. Let's see an example of this:

@Composable
fun ArtistCard(/*...*/) {
    val padding = 16.dp
    Column(
        Modifier
            .clickable(onClick = onClick)
            .padding(padding)
            .fillMaxWidth()
    ) {
        // rest of the implementation
    }
}

The entire area, including the padding around the edges, responds to clicks

In the code above the whole area is clickable, including the surrounding padding, because the padding modifier has been applied after the clickable modifier. If the modifiers order is reversed, the space added by padding does not react to user input:

@Composable
fun ArtistCard(/*...*/) {
    val padding = 16.dp
    Column(
        Modifier
            .padding(padding)
            .clickable(onClick = onClick)
            .fillMaxWidth()
    ) {
        // rest of the implementation
    }
}

The padding around the edge of the layout no longer responds to clicks

Built-in modifiers

Jetpack Compose provides a list of built-in modifiers to help you decorate or augment a composable. Some modifiers such as padding, clickable, and fillMaxWidth have already been introduced. Here’s a list of other common modifiers:

size

By default, layouts provided in Compose wrap their children. However, you can set a size by using the size modifier:

@Composable
fun ArtistCard(/*...*/) {
    Row(
        modifier = Modifier.size(width = 400.dp, height = 100.dp)
    ) {
        Image(/*...*/)
        Column { /*...*/ }
    }
}

Note that the size you specified might not be respected if it does not satisfy the constraints coming from the layout's parent. If you require the composable size to be fixed regardless of the incoming constraints, use the requiredSize modifier:

@Composable
fun ArtistCard(/*...*/) {
    Row(
        modifier = Modifier.size(width = 400.dp, height = 100.dp)
    ) {
        Image(
            /*...*/
            modifier = Modifier.requiredSize(150.dp)
        )
        Column { /*...*/ }
    }
}

Child image is bigger than the constraints coming from its parent

In this example, even with the parent height set to 100.dp, the height of the Image will be 150.dp, as the requiredSize modifier takes precedence.

If you want a child layout to fill all the available height allowed by the parent, add the fillMaxHeight modifier (Compose also provides fillMaxSize and fillMaxWidth):

@Composable
fun ArtistCard(/*...*/) {
    Row(
        modifier = Modifier.size(width = 400.dp, height = 100.dp)
    ) {
        Image(
            /*...*/
            modifier = Modifier.fillMaxHeight()
        )
        Column { /*...*/ }
    }
}

The image height is as big as its parent

If you want to add padding above a text baseline such that you achieve a specific distance from the top of the layout to the baseline, use the paddingFromBaseline modifier:

@Composable
fun ArtistCard(artist: Artist) {
    Row(/*...*/) {
        Column {
            Text(
                text = artist.name,
                modifier = Modifier.paddingFromBaseline(top = 50.dp)
            )
            Text(artist.lastSeenOnline)
        }
    }
}

Text with padding above it

Offset

To position a layout relative to its original position, add the offset modifier and set the offset in the x and y axis. Offsets can be positive as well as non-positive. The difference between padding and offset is that adding an offset to a composable does not change its measurements:

@Composable
fun ArtistCard(artist: Artist) {
    Row(/*...*/) {
        Column {
            Text(artist.name)
            Text(
                text = artist.lastSeenOnline,
                modifier = Modifier.offset(x = 4.dp)
            )
        }
    }
}

Text shifted to the right side of its parent container

The offset modifier is applied horizontally according to the layout direction. In a left-to-right context, a positive offset shifts the element to the right, while in a right-to-left context, it shifts the element to the left. If you need to set an offset without considering layout direction, see the absoluteOffset modifier, in which a positive offset value always shifts the element to the right.

Type safety in Compose

In Compose, there are modifiers that only work when applied to children of certain composables. For example, if you want to make a child as big as the parent Box without affecting the Box size, use the matchParentSize modifier.

Compose enforces this type safety by means of custom scopes. For example, matchParentSize is only available in BoxScope. Therefore, it can only be used when the child is used within a Box.

Scoped modifiers notify the parent about some information the parent should know about the child. These are also commonly referred to as parent data modifiers. Their internals are different from the general purpose modifiers, but from an usage perspective, these differences don't matter.

matchParentSize in Box

As mentioned above, if you want a child layout to be the same size as a parent Box without affecting the Box size, use the matchParentSize modifier.

Note that matchParentSize is only available within a Box scope, meaning that it only applies to direct children of Box composables.

In the example below, the child Spacer takes its size from its parent Box, which in turn takes its size from the biggest children, ArtistCard in this case.

@Composable
fun MatchParentSizeComposable() {
    Box {
        Spacer(Modifier.matchParentSize().background(Color.LightGray))
        ArtistCard()
    }
}

Gray background filling its container

If fillMaxSize were used instead of matchParentSize, the Spacer would take all the available space allowed to the parent, in turn causing the parent to expand and fill all the available space.

Gray background filling the screen

weight in Row and Column

As you have seen in the previous section on Padding and size, by default, a composable size is defined by the content it’s wrapping. You can set a composable size to be flexible within its parent using the weight Modifier that is only available in RowScope, and ColumnScope.

Let’s take a Row that contains two Box composables. The first box is given twice the weight of the second, so it's given twice the width. Since the Row is 210.dp wide, the first Box is 140.dp wide, and the second is 70.dp:

@Composable
fun ArtistCard(/*...*/) {
    Row(
        modifier = Modifier.fillMaxWidth()
    ) {
        Image(
            /*...*/
            modifier = Modifier.weight(2f)
        )
        Column(
            modifier = Modifier.weight(1f)
        ) {
            /*...*/
        }
    }
}

The image width is twice text width

Scrollable layouts

Learn more about scrollable layouts in the Compose gestures documentation.

For lists and lazy lists, check out the Compose lists documentation.

Responsive layouts

A layout should be designed with consideration of different screen orientations and form factor sizes. Compose offers out of the box a few mechanisms to facilitate adapting your composable layouts to various screen configurations.

Constraints

In order to know the constraints coming from the parent and design the layout accordingly, you can use a BoxWithConstraints. The measurement constraints can be found in the scope of the content lambda. You can use these measurement constraints to compose different layouts for different screen configurations:

@Composable
fun WithConstraintsComposable() {
    BoxWithConstraints {
        Text("My minHeight is $minHeight while my maxWidth is $maxWidth")
    }
}

Slot-based layouts

Compose provides a large variety of composables based on Material Design with the androidx.compose.material:material dependency (included when creating a Compose project in Android Studio) to make UI building easy. Elements like Drawer, FloatingActionButton, and TopAppBar are all provided.

Material components make heavy use of slot APIs, a pattern Compose introduces to bring in a layer of customization on top of composables. This approach makes components more flexible, as they accept a child element which can configure itself rather than having to expose every configuration parameter of the child. Slots leave an empty space in the UI for the developer to fill as they wish. For example, these are the slots that you can customize in a TopAppBar:

A diagram showing the available slots in a Material Components app bar

Composables usually take a content composable lambda ( content: @Composable () -> Unit). Slot APIs expose multiple content parameters for specific uses. For example, TopAppBar allows you to provide the content for title, navigationIcon, and actions.

For example, Scaffold allows you to implement a UI with the basic Material Design layout structure. Scaffoldprovides slots for the most common top-level Material components, such as TopAppBar, BottomAppBar, FloatingActionButton, and Drawer. By using Scaffold, it's easy to make sure these components are properly positioned and work together correctly.

The JetNews sample app, which uses Scaffold to position multiple elements

@Composable
fun HomeScreen(/*...*/) {
    Scaffold(
        drawerContent = { /*...*/ },
        topBar = { /*...*/ },
        content = { /*...*/ }
    )
}