Jetpack Compose executes modifiers across distinct phases. The following breakdown shows which phase common modifiers belong to and when their arguments or lambdas are evaluated.
Phase 1: Composition phase
These modifiers evaluate arguments synchronously inside the @Composable
body. Any state read here registers a dependency with the Composable restart
scope and triggers a full recomposition when changed.
- Static Dimension & Sizing Modifiers:
Modifier.size(dp),Modifier.width(dp),Modifier.height(dp)Modifier.fillMaxSize(),Modifier.fillMaxWidth(),Modifier.fillMaxHeight()Modifier.wrapContentSize(),Modifier.wrapContentWidth(),Modifier.wrapContentHeight()Modifier.requiredSize(),Modifier.defaultMinSize()Modifier.aspectRatio(ratio)Modifier.weight(weight)(inRowScopeorColumnScope)Modifier.width(IntrinsicSize.Min / Max)Modifier.height(IntrinsicSize.Min / Max)
- Static Spacing & Insets:
Modifier.padding(all = dp),Modifier.padding(start, top, end, bottom)Modifier.windowInsetsPadding(insets),Modifier.statusBarsPadding()
- Direct Value Visual Modifiers:
Modifier.alpha(float)(reads state during composition)Modifier.rotate(degrees)(reads state during composition)Modifier.scale(scaleX, scaleY)(reads state during composition)Modifier.background(color = Color, shape = Shape)Modifier.border(width, color, shape)Modifier.shadow(elevation, shape)Modifier.clip(shape)Modifier.blur(radius)
- Static Position Modifiers:
Modifier.offset(x = dp, y = dp)(direct value overload)Modifier.absoluteOffset(x = dp, y = dp)
- Dynamic Composable Modifiers:
Modifier.composed { ... }(runs the factory body during composition)
Phase 2: Layout phase
The Layout phase is split into two distinct sub-steps with separate invalidation scopes:
Step 2A: Measurement
Determines the size (width, height) and constraints. State read here
triggers re-measurement and re-placement:
Modifier.layout { measurable, constraints -> ... }: Code beforelayout(w, h)executes during measurement.
Step 2B: Placement & layout callbacks
Determines the (x, y) coordinate position. State read here triggers
re-placement only (skipping measurement and composition):
Modifier.offset { IntOffset(x, y) }(lambda overload)Modifier.absoluteOffset { IntOffset(x, y) }(lambda overload)Modifier.layout { ... layout(w, h) { placeable.place(x, y) } }: Code inside thelayout(w, h) { ... }block.Modifier.onPlaced { layoutCoordinates -> ... }: Invoked after parent coordinates are determined during placement.Modifier.onSizeChanged { intSize -> ... }: Invoked after layout measurement completes if the size changed.Modifier.onGloballyPositioned { layoutCoordinates -> ... }: Invoked after the entire window layout pass finishes.
Phase 3: Drawing phase
These modifiers execute during canvas drawing. State read inside these blocks skips both Composition and Layout, running only the draw pass:
Modifier.graphicsLayer { ... }:- Properties updated in lambda:
alpha,translationX,translationY,scaleX,scaleY,rotationX,rotationY,rotationZ,shadowElevation,shape,clip,renderEffect,transformOrigin.
- Properties updated in lambda:
Modifier.drawBehind { ... }:- Draws canvas commands behind the composable content.
Modifier.drawWithContent { ... }:- Controls drawing order with
drawContent().
- Controls drawing order with
Modifier.drawWithCache { ... }:- Outer block runs on size or layout changes to cache
Path,Paint, orBrushobjects;onDrawBehindoronDrawWithContentruns on each frame draw.
- Outer block runs on size or layout changes to cache
Outside of recomposition loop - observation modifiers
Some modifiers operate outside the three frame phases to handle accessibility, user input, and lifecycle observations.
Semantics & accessibility
Processes semantic properties for accessibility services (TalkBack) and testing:
Modifier.semantics { ... }Modifier.clearAndSetSemantics { ... }
Input, gestures & events
Operates outside the three-phase frame rendering pipeline, receiving OS motion events and user interactions:
Modifier.pointerInput(key) { detectTapGestures { ... } }/detectDragGestures/awaitPointerEventScopeModifier.clickable(enabled, onClickLabel) { ... }Modifier.combinedClickable { ... }Modifier.toggleable { ... }Modifier.selectable { ... }Modifier.draggable(...)/Modifier.scrollable(...)/Modifier.draggable2D(...)Modifier.onFocusChanged { focusState -> ... }Modifier.onKeyEvent { keyEvent -> ... }Modifier.onVisibilityChanged { isVisible -> ... }: The callback is invoked when visibility of a composable changes after configured delay, in a coroutine.
Quick Reference: Value versus Lambda phase
Reading a state value in a lambda defers the read to a later phase, which skips earlier phases when that state changes:
| Modifier / Pattern | Direct Value (Composition) | Lambda Variant (Deferred Phase) | When State Changes |
|---|---|---|---|
| Offset | Modifier.offset(x, y) |
Modifier.offset { IntOffset(x, y) } |
Layout (Placement) (Skips Composition) |
| Alpha / Opacity | Modifier.alpha(alpha) |
Modifier.graphicsLayer { this.alpha = alpha } |
Drawing (Skips Composition & Layout) |
| Rotation | Modifier.rotate(degrees) |
Modifier.graphicsLayer { rotationZ = degrees } |
Drawing (Skips Composition & Layout) |
| Scale | Modifier.scale(scale) |
Modifier.graphicsLayer { scaleX = s; scaleY = s } |
Drawing (Skips Composition & Layout) |
| Translation | Modifier.padding(...) |
Modifier.graphicsLayer { translationX = ... } |
Drawing (Skips Composition & Layout) |
| Background Color | Modifier.background(color) |
Modifier.drawBehind { drawRect(color) } |
Drawing (Skips Composition & Layout) |
| Scroll / Parallax | Modifier.offset(scroll.dp) |
Modifier.offset { IntOffset(0, scroll) } |
Layout (Placement) (Skips Composition) |
Modifier.Node interface mapping
For custom modifiers using the high-performance Modifier.Node API, phases map
directly to node capability interfaces:
| Phase | Modifier.Node Interface |
Core Methods |
|---|---|---|
| Layout (Measure & Place) | LayoutModifierNode |
measure(measurable, constraints) |
| Layout (Global Coordinates) | GlobalPositionAwareModifierNode |
onGloballyPositioned(coordinates) |
| Layout (Placement) | LayoutAwareModifierNode |
onRemeasured(size), onPlaced(coordinates) |
| Drawing | DrawModifierNode |
draw(ContentDrawScope) |
| Input / Pointer | PointerInputModifierNode |
onPointerEvent(...), onCancelPointerInput() |
| Semantics | SemanticsModifierNode |
applySemantics(SemanticsPropertyReceiver) |
| Focus | FocusEventModifierNode |
onFocusEvent(focusState) |