Baseline Alignment and Intrinsic Measurements in Compose

Quick answer: use alignByBaseline() or alignBy(FirstBaseline) for text that should share a typographic baseline inside a Row. Use Modifier.height(IntrinsicSize.Min) when a parent needs the minimum height required by its children before it can size another child, such as a divider between two text blocks. Intrinsic measurements are a specialized layout tool, not a replacement for ordinary constraints.

Compose normally measures a child once and then places it. That single-pass model is efficient, but it creates two common layout questions:

  • How do I align a small label with the baseline of a larger title instead of centering their boxes?
  • How can a divider match the height of text that may wrap to different numbers of lines?

Alignment lines and intrinsic measurements answer those questions at different levels.

Baseline alignment in a Row

The baseline is an invisible horizontal line used by text layout. Text with different font sizes can have different top and bottom edges, but aligning their first baselines makes them read as one typographic line:

import androidx.compose.foundation.layout.Row
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
import androidx.compose.ui.unit.sp

@Composable
fun PriceLabel() {
    Row {
        Text(
            text = "$24",
            fontSize = 32.sp,
            modifier = Modifier.alignByBaseline(),
        )
        Text(
            text = " / month",
            fontSize = 14.sp,
            modifier = Modifier.alignByBaseline(),
        )
    }
}

alignByBaseline() is a RowScope modifier. It places the participating children so their first baselines coincide. Children that do not participate are positioned using the row’s normal vertical alignment rules.

This differs from verticalAlignment = Alignment.CenterVertically, which centers the children’s layout boxes. Centering is often correct for an icon and a label, but it can make adjacent text look too high or too low when the type sizes differ. The Compose alignment-line documentation explains why a text baseline is an alignment line and how parents use it after measurement.

Use a specific alignment line

Text exposes FirstBaseline and LastBaseline. You can use the explicit line when the intent is clearer than the convenience modifier:

import androidx.compose.ui.layout.FirstBaseline

@Composable
fun HeadingWithMeta() {
    Row {
        Text(
            text = "Inbox",
            modifier = Modifier.alignBy(FirstBaseline),
        )
        Text(
            text = "12 unread",
            modifier = Modifier.alignBy(FirstBaseline),
        )
    }
}

All children using alignBy form an alignment group within that Row. A child without an alignment modifier is not automatically added to the group. If you need an icon or custom layout to join the group, provide an alignment position with the lambda overload:

Row {
    Text(
        text = "Inbox",
        modifier = Modifier.alignByBaseline(),
    )
    Icon(
        imageVector = Icons.Default.Mail,
        contentDescription = "",
        modifier = Modifier.alignBy { measured ->
            measured.measuredHeight / 2
        },
    )
}

The lambda returns the alignment-line position in pixels inside the measured child. Dividing an icon’s height by two is only an illustration; choose a position that matches the visual design and test it with the actual icon and font metrics. Use an empty content description only when the icon is genuinely decorative and its surrounding text already communicates the action.

Baseline padding with paddingFromBaseline

Sometimes the requirement is not sibling alignment but a precise distance from a text baseline to a container edge. paddingFromBaseline expresses that directly:

Text(
    text = "Profile",
    modifier = Modifier.paddingFromBaseline(top = 32.dp),
)

This says that the top of the layout should be 32dp above the first line’s baseline. It is more precise for typography-driven spacing than guessing with ordinary top padding, because ordinary padding measures from the text layout’s top edge.

What intrinsic measurements are

Intrinsic measurements let a parent ask a child what size it would need before the normal measurement pass. Compose exposes four queries:

QueryQuestion
minIntrinsicWidth(height)What is the narrowest width that can display the content for this height?
maxIntrinsicWidth(height)What is the widest useful width for this height?
minIntrinsicHeight(width)What is the minimum height needed for this width?
maxIntrinsicHeight(width)At what height does more height stop reducing the width?

In application code, you usually request these through IntrinsicSize.Min or IntrinsicSize.Max:

Row(
    modifier = Modifier.height(IntrinsicSize.Min),
) {
    // Children are measured after the row has queried their intrinsic height.
}

IntrinsicSize.Min does not mean “take the smallest child.” It asks the layout to use the minimum intrinsic size required by its content. For a Row, the resulting minimum height is influenced by the tallest child’s minimum intrinsic height at the relevant width.

The official intrinsic measurement guide is explicit about why this API exists: Compose should not measure ordinary children more than once in a pass, but a parent can query intrinsic information before measuring them.

The classic divider example

Suppose two text blocks sit on either side of a vertical divider. Their text can wrap, so the divider should match the taller block. height(IntrinsicSize.Min) gives the Row a height based on its children, then the divider fills that resolved height:

import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.fillMaxHeight
import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.width
import androidx.compose.material3.VerticalDivider
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
import androidx.compose.ui.unit.dp

@Composable
fun TwoDescriptions(
    left: String,
    right: String,
) {
    Row(
        modifier = Modifier.height(IntrinsicSize.Min),
    ) {
        Text(
            text = left,
            modifier = Modifier.weight(1f),
        )
        Spacer(Modifier.width(16.dp))
        VerticalDivider(
            modifier = Modifier.fillMaxHeight(),
        )
        Spacer(Modifier.width(16.dp))
        Text(
            text = right,
            modifier = Modifier.weight(1f),
        )
    }
}

The intrinsic query lets the Row determine the minimum height required by the text children. fillMaxHeight() then gives VerticalDivider the row’s resolved height. Without the intrinsic modifier, a divider may have no useful height when the row is only as tall as its content and the divider itself does not establish a height.

The exact result still depends on width constraints. A narrow parent can make either text wrap into more lines, increasing the row’s intrinsic height. This is why the component should be previewed at both narrow and wide widths, as described in the Compose preview guide.

Intrinsic size is not a general layout shortcut

Avoid adding IntrinsicSize.Min to every Row or Column. It asks the layout system for extra information and can make a complex hierarchy harder to reason about. Prefer ordinary constraints when the parent already knows the required size:

Row(
    modifier = Modifier.height(48.dp),
) {
    Text("Fixed-height row")
    VerticalDivider(Modifier.fillMaxHeight())
}

Use intrinsics when the size truly depends on the content and an adjacent child must react to that size. If you control the component API, an explicit minHeight, a measured state, or a custom layout may communicate the design more directly.

Custom layouts and intrinsic overrides

Built-in layouts provide useful intrinsic behavior. A custom Layout or layout modifier receives automatically calculated approximations for intrinsic measurements, but those defaults may not represent a specialized measurement policy correctly.

If callers use IntrinsicSize with a custom layout, implement the relevant methods on its MeasurePolicy:

val measurePolicy = object : MeasurePolicy {
    override fun MeasureScope.measure(
        measurables: List<Measurable>,
        constraints: Constraints,
    ): MeasureResult {
        // Measure and place children according to the layout's policy.
        TODO("Implement measurement")
    }

    override fun IntrinsicMeasureScope.minIntrinsicHeight(
        measurables: List<IntrinsicMeasurable>,
        width: Int,
    ): Int {
        return measurables.maxOfOrNull { it.minIntrinsicHeight(width) } ?: 0
    }
}

The snippet is illustrative and intentionally omits the full custom layout implementation. Override minIntrinsicWidth, minIntrinsicHeight, maxIntrinsicWidth, and maxIntrinsicHeight when the defaults are not accurate for your layout. The custom layouts guide covers the measurement policy and the single-measure rule.

Common mistakes

Centering when you need baseline alignment

Alignment.CenterVertically aligns boxes, not glyph baselines. Use alignByBaseline() for text siblings with different sizes.

Applying alignByBaseline() outside a Row

The modifier is scoped to RowScope. It is not a general-purpose modifier for arbitrary parents. For a Column, the relevant alignment direction and available alignment lines are different.

Expecting a divider to define the row’s height

A divider that only uses fillMaxHeight() consumes the height offered by its parent; it does not necessarily create that height. Give the parent a fixed height or use intrinsic sizing when the content should define the height.

Using intrinsics to measure a child twice manually

Do not call measure() twice on the same Measurable to discover a size. Compose’s normal layout model permits one measurement per child in a pass. Use intrinsic APIs or redesign the measurement policy.

Forgetting width when debugging intrinsic height

Text intrinsic height depends on the width it is allowed to use. Test the component under the same width constraints it will receive in the real screen.

A short decision guide

  • Align text on one visual line: use alignByBaseline().
  • Align a child to a known custom line: use alignBy(alignmentLine) or the lambda overload.
  • Space a container from a text baseline: use paddingFromBaseline().
  • Make a sibling match content-driven height: consider height(IntrinsicSize.Min).
  • Build a specialized measurement policy: use Layout, then test constraints and intrinsic behavior explicitly.

Baseline alignment solves a positioning problem after measurement. Intrinsic measurement solves a sizing problem before measurement. Keeping that distinction in mind makes both APIs easier to use—and helps you avoid reaching for intrinsic sizing when a normal constraint or fixed dimension is enough.