Recomposed
Modifier

Modifier Order Matters: It's a Wrapper Chain, Not a Property Bag

Oct 4, 2026 · 2 min read

Modifier Order Matters: It's a Wrapper Chain, Not a Property Bag

The intuition

Modifier.a().b().c() reads like you're setting three independent properties on a composable. You aren't. Each modifier wraps everything that comes after it in the chain, so order changes what each modifier sees as its input: available size, content, touch target. The same modifiers in a different order produce different pixels and different behavior.

Classic example: padding vs background

// padding wraps background: available space shrinks first, then background
// paints inside what's left. No red visible in the padding gap.
Modifier.padding(16.dp).background(Color.Red)
 
// background wraps padding: background paints the full size first, then
// padding just pushes the content inward. Red fills the whole area.
Modifier.background(Color.Red).padding(16.dp)

Same shape, same two modifiers, different order, different result, because you're not setting properties, you're nesting wrappers.

Same logic, other pairs

  • clickable().padding() vs padding().clickable(): the ripple and touch target are whatever size the node had at the point clickable was inserted in the chain. Put clickable before padding and the padding area is clickable too; put it after and it isn't.
  • size().clip() vs clip().size(), border() before/after clip(): same mechanism, whichever comes first constrains what the next one operates on.
  • Draw order follows the same rule: modifier order in the chain is draw order too, which is why .background().padding() and .padding().background() look different both in layout and in paint order.

The fundamental

This is function composition: Modifier.a().b() behaves like a(b(x)). Function composition is non-commutative: f(g(x)) ≠ g(f(x)) in general. Equivalently, it's the decorator pattern: each decorator wraps the object handed to it, and wrapping order changes behavior because each layer only sees what's inside it, not what's outside it.

Elsewhere: same fundamental, different framework

  • OkHttp interceptors: the interceptor chain runs in registration order, and each interceptor wraps the next. A logging interceptor placed before a retry interceptor only sees the first attempt; placed after, it sees every retried attempt. Same "wrapper order changes what each layer can observe" mechanism.
  • CSS transform: transform: translate(50px) rotate(45deg) vs rotate(45deg) translate(50px) gives different results, because each transform function operates in the coordinate space left behind by the one before it.

Mental model to actually use

Stop asking "which properties am I setting." Ask instead: "what does this modifier see as its input: size, content, touch target, based on what's to its left in the chain?" That question is enough to predict the result every time.

Get new posts in your inbox