Layouts in Compose

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, and shows you how to build more specialized layouts when you need them.

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 one element on top of another.

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

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.

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 AlignInRow() {
    Row(
        modifier = Modifier
            .size(150.dp)
            .background(Color.Yellow),
        horizontalArrangement = Arrangement.End,
        verticalAlignment = Alignment.CenterVertically
    ) {
        Box(Modifier.size(50.dp).background(Color.Red))
        Box(Modifier.size(50.dp).background(Color.Blue))
    }
}

A large yellow square, with a row of boxes across its center

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.

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. Here’s a list of the ones that cover most common use cases:

Padding and size

To set padding around a composable, add the padding modifier:

@Composable
fun PaddedComposable() {
    Text("Hello World", modifier = Modifier.background(Color.Green).padding(20.dp))
}

Text with padding all around it

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

@Composable
fun SizedComposable() {
    Box(Modifier.size(100.dp, 100.dp).background(Color.Red))
}

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 FixedSizeComposable() {
    Box(Modifier.size(90.dp, 150.dp).background(Color.Green)) {
        Box(Modifier.requiredSize(100.dp, 100.dp).background(Color.Red))
    }
}

Two rectangles one on top of the other, a wider red box on top of a narrower green background

In this example, even with the parent width set to 90.dp, the width of the inner Box will be 100.dp, as the inner box's requiredSize modifier takes precedence.

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

@Composable
fun FillSizeComposable() {
    Box(Modifier.background(Color.Green).size(50.dp).padding(10.dp)) {
        Box(Modifier.background(Color.Blue).fillMaxSize())
    }
}

A blue square in the center of a larger green square

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 inner Spacer takes its size from its parent Box, which in turn takes its size from theText it contains.

@Composable
fun MatchParentSizeComposable() {
    Box {
        Spacer(Modifier.matchParentSize().background(Color.Green))
        Text("Hello World")
    }
}

Text 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.

Small text in the upper corner of a larger green square

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 TextWithPaddingFromBaseline() {
    Box(Modifier.background(Color.Yellow)) {
        Text("Hi there!", Modifier.paddingFromBaseline(top = 32.dp))
    }
}

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 OffsetComposable() {
    Box(Modifier.background(Color.Yellow).size(width = 150.dp, height = 70.dp)) {
        Text(
            "Layout offset modifier sample",
            Modifier.offset(x = 15.dp, y = 20.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.

Scrollable layouts

Learn more about scrollable layouts in the Compose gestures 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.

Weight modifier in Row and Column

As you have seen in the previous section on Padding and size, a composable size is defined by the content it’s wrapping by default. You can set a composable size to be flexible within its parent. Let’s take a Row that contains two 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 FlexibleComposable() {
    Row(Modifier.width(210.dp)) {
        Box(Modifier.weight(2f).height(50.dp).background(Color.Blue))
        Box(Modifier.weight(1f).height(50.dp).background(Color.Red))
    }
}

Two rectangles side by side, a wide blue rectangle to the left of a narrower red rectangle

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 = { /*...*/ },
        bodyContent = { /*...*/ }
    )
}

ConstraintLayout

ConstraintLayout can help place composables relative to others on the screen, and is an alternative to using multiple nested Row, Column, Box and custom layouts elements. ConstraintLayout is useful when implementing larger layouts with more complicated alignment requirements but when creating simple layouts, prefer using Columns and Rows instead.

To use ConstraintLayout in Compose, you need to add this dependency in your build.gradle:

implementation "androidx.constraintlayout:constraintlayout-compose:1.0.0-alpha03"

ConstraintLayout in Compose works with a DSL:

  • References are created using createRefs() or createRefFor(), and each composable in ConstraintLayoutneeds to have a reference associated with it.
  • Constraints are provided using the constrainAs() modifier, which takes the reference as a parameter and lets you specify its constraints in the body lambda.
  • Constraints are specified using linkTo() or other helpful methods.
  • parent is an existing reference that can be used to specify constraints towards the ConstraintLayout composable itself.

Here's an example of a composable using a ConstraintLayout:

@Composable
fun ConstraintLayoutContent() {
    ConstraintLayout {
        // Create references for the composables to constrain
        val (button, text) = createRefs()

        Button(
            onClick = { /* Do something */ },
            // Assign reference "button" to the Button composable
            // and constrain it to the top of the ConstraintLayout
            modifier = Modifier.constrainAs(button) {
                top.linkTo(parent.top, margin = 16.dp)
            }
        ) {
            Text("Button")
        }

        // Assign reference "text" to the Text composable
        // and constrain it to the bottom of the Button composable
        Text("Text", Modifier.constrainAs(text) {
            top.linkTo(button.bottom, margin = 16.dp)
        })
    }
}

This code constrains the top of the Button to the parent with a margin of 16.dp and a Text to the bottom of the Button also with a margin of 16.dp.

Shows a button and a text element arranged in a ConstraintLayout

For more examples of how to work with ConstraintLayout, try the layouts codelab.

Decoupled API

In the ConstraintLayout example, constraints are specified inline, with a modifier in the composable they're applied to. However, there are situations when it's preferable to decouple the constraints from the layouts they apply to. For example, you might want to change the constraints based on the screen configuration, or animate between two constraint sets.

For cases like these, you can use ConstraintLayout in a different way:

  1. Pass in a ConstraintSet as a parameter to ConstraintLayout.
  2. Assign references created in the ConstraintSet to composables using the layoutId modifier.
@Composable
fun DecoupledConstraintLayout() {
    BoxWithConstraints {
        val constraints = if (minWidth < 600.dp) {
            decoupledConstraints(margin = 16.dp) // Portrait constraints
        } else {
            decoupledConstraints(margin = 32.dp) // Landscape constraints
        }

        ConstraintLayout(constraints) {
            Button(
                onClick = { /* Do something */ },
                modifier = Modifier.layoutId("button")
            ) {
                Text("Button")
            }

            Text("Text", Modifier.layoutId("text"))
        }
    }
}

private fun decoupledConstraints(margin: Dp): ConstraintSet {
    return ConstraintSet {
        val button = createRefFor("button")
        val text = createRefFor("text")

        constrain(button) {
            top.linkTo(parent.top, margin = margin)
        }
        constrain(text) {
            top.linkTo(button.bottom, margin)
        }
    }
}

Then, when you need to change the constraints, you can just pass a different ConstraintSet.

Custom layouts

In Compose, UI elements are represented by the composable functions that emit a piece of UI when invoked, that is then added to a UI tree that gets rendered on the screen. Each UI element has one parent and potentially many children. Each element is also located within its parent, specified as an (x, y) position, and a size, specified as a width and a height.

Parents define the constraints for their child elements. An element is asked to define its size within those constraints. Constraints restrict the minimum and maximum width and height of an element. If an element has child elements, it may measure each child to help determine its size. Once an element determines and reports its own size, it has an opportunity to define how to place its child elements relative to itself, as described in detail in Creating custom layouts.

Single-pass measurement is good for performance, allowing Compose to efficiently handle deep UI trees. If an element measured its child twice and that child measured one of its children twice and so on, a single attempt to lay out a whole UI would have to do a lot of work, making it hard to keep your app performing well. However, there are times when you really need additional information on top of what a single child measurement would tell you. There are approaches that can efficiently cope with a situation like that, which are discussed in the intrinsic measurements section.

Using the layout modifier

You can use the layout modifier to modify how an element is measured and laid out. Layout is a lambda; its parameters include the element you can measure, passed as measurable, and that composable's incoming constraints, passed as constraints. A custom layout modifier can look like this:

fun Modifier.customLayoutModifier(...) =
    this.layout { measurable, constraints ->
        ...
    })

Let's display a Text on the screen and control the distance from the top to the baseline of the first line of text. This is exactly what the paddingFromBaseline modifier does, we’re implementing it here as an exemple. To do that, use the layout modifier to manually place the composable on the screen. Here's the desired behavior where the Text top padding is set 24.dp:

Shows the difference between normal UI padding, which sets the space between elements, and text padding that sets the space from one baseline to the next

Here's the code to produce that spacing:

fun Modifier.firstBaselineToTop(
    firstBaselineToTop: Dp
) = layout { measurable, constraints ->
    // Measure the composable
    val placeable = measurable.measure(constraints)

    // Check the composable has a first baseline
    check(placeable[FirstBaseline] != AlignmentLine.Unspecified)
    val firstBaseline = placeable[FirstBaseline]

    // Height of the composable with padding - first baseline
    val placeableY = firstBaselineToTop.roundToPx() - firstBaseline
    val height = placeable.height + placeableY
    layout(placeable.width, height) {
        // Where the composable gets placed
        placeable.placeRelative(0, placeableY)
    }
}

Here's what's going on in that code:

  1. In the measurablelambda parameter, you measure the Text represented by the measurable parameter by calling measurable.measure(constraints).
  2. You specify the size of the composable by calling the layout(width, height) method, which also gives a lambda used for placing the wrapped elements. In this case, it's the height between the last baseline and added top padding.
  3. You position the wrapped elements on the screen by calling placeable.place(x, y). If the wrapped elements aren't placed, they won't be visible. The yposition corresponds to the top padding - the position of the first baseline of the text.

To verify this works as expected, use this modifier on a Text:

@Preview
@Composable
fun TextWithPaddingToBaselinePreview() {
    MyApplicationTheme {
        Text("Hi there!", Modifier.firstBaselineToTop(32.dp))
    }
}

@Preview
@Composable
fun TextWithNormalPaddingPreview() {
    MyApplicationTheme {
        Text("Hi there!", Modifier.padding(top = 32.dp))
    }
}

Multiple previews of text elements; one shows ordinary padding between elements, the other shows padding from one baseline to the next

Creating custom layouts

The layout modifier only changes the calling composable. To measure and layout multiple composables, use the Layout composable instead. This composable allows you to measure and lay out children manually. All higher-level layouts like Column and Row are built with the Layout composable.

Let's build a very basic version of Column. Most custom layouts follow this pattern:

@Composable
fun MyBasicColumn(
    modifier: Modifier = Modifier,
    content: @Composable() () -> Unit
) {
    Layout(
        modifier = modifier,
        children = content
    ) { measurables, constraints ->
        // measure and position children given constraints logic here
    }
}

Similarly to the layout modifier, measurables is the list of children that need to be measured and constraints are the constraints from the parent. Following the same logic as before, MyBasicColumn can be implemented like this:

@Composable
fun MyBasicColumn(
    modifier: Modifier = Modifier,
    content: @Composable() () -> Unit
) {
    Layout(
        modifier = modifier,
        content = content
    ) { measurables, constraints ->
        // Don't constrain child views further, measure them with given constraints
        // List of measured children
        val placeables = measurables.map { measurable ->
            // Measure each children
            measurable.measure(constraints)
        }

        // Set the size of the layout as big as it can
        layout(constraints.maxWidth, constraints.maxHeight) {
            // Track the y co-ord we have placed children up to
            var yPosition = 0

            // Place children in the parent layout
            placeables.forEach { placeable ->
                // Position item on the screen
                placeable.placeRelative(x = 0, y = yPosition)

                // Record the y co-ord placed up to
                yPosition += placeable.height
            }
        }
    }
}

The child composables are constrained by the Layout constraints (without the minHeight constraints), and they're placed based on the yPosition of the previous composable.

Here's how that custom composable would be used:

@Composable
fun CallingComposable(modifier: Modifier = Modifier) {
    MyBasicColumn(modifier.padding(8.dp)) {
        Text("MyBasicColumn")
        Text("places items")
        Text("vertically.")
        Text("We've done it by hand!")
    }
}

Several text elements stacked one above the next in a column.

Layout direction

Change the layout direction of a composable by changing the LocalLayoutDirection compositionLocal.

If you're placing composables manually on the screen, the LayoutDirection is part of the LayoutScope of the layout modifier or Layout composable.

When using layoutDirection, place composables using place. Unlike the place method, place doesn't change based on the reading direction (left-to-right versus right-to-left).

Intrinsic measurements

One of the rules of Compose is that you should only measure your children once; measuring children twice throws a runtime exception. However, there are times when you need some information about your children before measuring them.

Intrinsics lets you query children before they're actually measured.

To a composable, you can ask for its intrinsicWidth or intrinsicHeight:

  • (min|max)IntrinsicWidth: Given this height, what's the minimum/maximum width you can paint your content properly?
  • (min|max)IntrinsicHeight: Given this width, what's the minimum/maximum height you can paint your content properly?

For example, if you ask the minIntrinsicHeight of a Text with infinite width, it'll return the height of the Text as if the text was drawn in a single line.

Intrinsics in action

Imagine that we want to create a composable that displays two texts on the screen separated by a divider like this:

Two text elements side by side, with a vertical divider between them

How can we do this? We can have a Row with two Texts inside that expands as much as they can and a Divider in the middle. We want the Divider to be as tall as the tallest Text and thin (width = 1.dp).

@Composable
fun TwoTexts(modifier: Modifier = Modifier, text1: String, text2: String) {
    Row(modifier = modifier) {
        Text(
            modifier = Modifier
                .weight(1f)
                .padding(start = 4.dp)
                .wrapContentWidth(Alignment.Start),
            text = text1
        )

        Divider(color = Color.Black, modifier = Modifier.fillMaxHeight().width(1.dp))
        Text(
            modifier = Modifier
                .weight(1f)

                .padding(end = 4.dp)
                .wrapContentWidth(Alignment.End),

            text = text2
        )
    }
}

@Preview
@Composable
fun TwoTextsPreview() {
    LayoutsCodelabTheme {
        Surface {
            TwoTexts(text1 = "Hi", text2 = "there")
        }
    }
}

If we preview this, we see that the Divider expands to the whole screen and that's not what we want:

Two text elements side by side, with a divider between them, but the divider stretches down below the bottom of the text

This happens because Row measures each child individually and the height of Text cannot be used to constraint the Divider. We want the Divider to fill the available space with a given height. For that, we can use the height(IntrinsicSize.Min) modifier .

height(IntrinsicSize.Min) sizes its children being forced to be as tall as their minimum intrinsic height. As it's recursive, it'll query Row and its children minIntrinsicHeight.

Applying that to our code, it'll work as expected:

@Composable
fun TwoTexts(modifier: Modifier = Modifier, text1: String, text2: String) {
    Row(modifier = modifier.preferredHeight(IntrinsicSize.Min)) {
        Text(
            modifier = Modifier
                .weight(1f)
                .padding(start = 4.dp)
                .wrapContentWidth(Alignment.Start),
            text = text1
        )

        Divider(color = Color.Black, modifier = Modifier.fillMaxHeight().width(1.dp))
        Text(
            modifier = Modifier
                .weight(1f)

                .padding(end = 4.dp)
                .wrapContentWidth(Alignment.End),
            text = text2
        )
    }
}

@Preview
@Composable
fun TwoTextsPreview() {
    LayoutsCodelabTheme {
        Surface {
            TwoTexts(text1 = "Hi", text2 = "there")
        }
    }
}

With preview:

Two text elements side by side, with a vertical divider between them

The Row composable's minIntrinsicHeight will be the maximum minIntrinsicHeight of its children. The Divider element's minIntrinsicHeight is 0 as it doesn't occupy space if no constraints are given; the Text minIntrinsicHeight will be that of the text given a specific width. Therefore, the Row element's height constraint will be the max minIntrinsicHeight of the Texts. Divider will then expand its height to the height constraint given by the Row.

Learn more

To learn more, try the Layouts in Jetpack Compose codelab.