← Back to list

10 Jetpack Compose Modifiers Every Dev Uses Wrong — Order Matters, Click Areas Break, Size…

Modifiers in Compose are the most powerful and most misunderstood API in the entire framework. They look like a simple chain of method…

Ramadan Sayed · 2026-04-26 16:44 · 3 claps · 12.7 min read paywalled
#modifier #jetpack-compose #jetpack-compose-modifier
Open on Medium ↗
Wiki topics: 📱 · Mobile Development

10 Jetpack Compose Modifiers Every Dev Uses Wrong — Order Matters, Click Areas Break, Size Conflicts Silently Resolve, and the Exact Fix for Each Mistake with Visual Diagrams

Modifiers in Compose are the most powerful and most misunderstood API in the entire framework. They look like a simple chain of method calls — .padding(16.dp).background(Color.Blue).clickable { } — but the ORDER you chain them determines everything: the visual appearance, the click target area, the measurement behavior, and even whether your composable renders at all.

The core mental model is this: modifiers apply outside-in, like wrapping layers. The first modifier in the chain is the outermost layer. Each subsequent modifier wraps inside the previous one. Getting this backwards produces bugs that look like the framework is broken — padding that doesn’t show, click areas that extend beyond the visible bounds, backgrounds that don’t fill the expected area, and borders that appear in the wrong place.

These are the 10 modifier mistakes I see most in production codebases — each with a visual diagram showing what goes wrong and the exact fix.

Mistake 1: Background Before Padding vs Padding Before Background

The Problem

This is the #1 modifier mistake in Compose. Developers expect .padding().background() to produce a colored box with padding inside it. Instead, it produces a colored box that's SMALLER than the available space, with a transparent gap around it.

// ❌ Background doesn't cover the full area — transparent gap visible
Box(
    modifier = Modifier
        .fillMaxWidth()
        .padding(16.dp)           // 1. OUTER LAYER: Shrink available space by 16dp on each side
        .background(Color.Blue)   // 2. INNER LAYER: Fill the remaining (smaller) area with blue
) {
    Text("Hello", color = Color.White)
}

// VISUAL RESULT:
// ┌──────────── parent ────────────┐
// │  16dp transparent gap          │
// │  ┌──── blue background ────┐  │
// │  │  Hello                   │  │
// │  └─────────────────────────┘  │
// │  16dp transparent gap          │
// └────────────────────────────────┘
// The blue box is 32dp narrower and 32dp shorter than the parent
// The 16dp gap is TRANSPARENT - parent background shows through

Why It Happens

Modifiers apply outside-in. Think of each modifier as a wrapper:

Layer 1 (outermost): .padding(16.dp)
  → "Shrink the space I give to my child by 16dp on each side"
  → The child (everything below in the chain) gets a SMALLER area

Layer 2 (inner): .background(Color.Blue)
  → "Fill MY area with blue"
  → My area = the SMALLER area from padding
  → Blue only fills the inner rectangle

The Fix

// ✅ Background fills the FULL area, padding shrinks the CONTENT area
Box(
    modifier = Modifier
        .fillMaxWidth()
        .background(Color.Blue)   // 1. OUTER: Fill full area with blue
        .padding(16.dp)           // 2. INNER: Shrink content area (blue still visible behind padding)
) {
    Text("Hello", color = Color.White)
}

// VISUAL RESULT:
// ┌──────── all blue ──────────────┐
// │                                 │
// │     Hello                       │
// │                                 │
// └─────────────────────────────────┘
// Blue fills everything. Content is 16dp inset from edges.
// This is what most developers expect.

The Double-Padding Pattern (Card with Margin)

// Common pattern: outer padding (margin) → background → inner padding (content spacing)
Card(
    modifier = Modifier
        .fillMaxWidth()
        .padding(horizontal = 16.dp, vertical = 4.dp)  // Margin FROM parent edges
) {
    Column(
        modifier = Modifier.padding(16.dp)  // Padding INSIDE the card
    ) {
        Text("Transfer Completed", style = MaterialTheme.typography.titleMedium)
        Text("$500.00 to Ahmed", style = MaterialTheme.typography.bodyMedium)
    }
}

Mistake 2: clickable Before clip (Click Area Doesn’t Match Visual Shape)

The Problem

When you make a circular button, the visual is clipped to a circle but the click area remains rectangular. Users tap outside the visible circle and the button responds — or worse, they tap in the corner of what looks like empty space and accidentally trigger the action.

// ❌ Click target is RECTANGULAR, but visual is CIRCULAR
Box(
    modifier = Modifier
        .size(56.dp)
        .clickable { onProfileClick() }   // 1. OUTER: Click area = full 56×56 RECTANGLE
        .clip(CircleShape)                 // 2. INNER: Visual clipped to circle
        .background(MaterialTheme.colorScheme.primary)
) {
    Icon(Icons.Default.Person, null, tint = Color.White, modifier = Modifier.align(Alignment.Center))
}

// VISUAL:              CLICK AREA:
//   ╭───╮              ┌─────────┐
//  │     │             │ ● ● ● ● │  ← Tapping HERE triggers click
//  │  👤  │             │ ●     ● │     even though nothing is visible!
//  │     │             │ ● ● ● ● │
//   ╰───╯              └─────────┘
// The corners are transparent but still clickable

Why It Happens

clickable creates a click target based on the area available WHEN IT'S APPLIED. Since it's applied before clip, the click target is the full rectangular area. The clip only affects visual rendering — it doesn't retroactively change the click area.

The Fix

// ✅ Clip FIRST, then clickable — click area matches the clipped shape
Box(
    modifier = Modifier
        .size(56.dp)
        .clip(CircleShape)                 // 1. OUTER: Defines the SHAPE (circle)
        .clickable { onProfileClick() }    // 2. INNER: Click area = the circle
        .background(MaterialTheme.colorScheme.primary)
) {
    Icon(Icons.Default.Person, null, tint = Color.White, modifier = Modifier.align(Alignment.Center))
}

// Now the click area is EXACTLY the visible circle
// Tapping outside the circle does nothing - correct behavior
// BONUS: The ripple effect also follows the circle shape
// (Because ripple is bounded to the clickable area, which is now circular)

Rounded Corners Example

// ✅ For rounded rectangle buttons:
Box(
    modifier = Modifier
        .fillMaxWidth()
        .clip(RoundedCornerShape(12.dp))   // Shape FIRST
        .clickable { onClick() }            // Click area matches rounded rect
        .background(MaterialTheme.colorScheme.primary)
        .padding(16.dp)
) {
    Text("Confirm Transfer", color = Color.White)
}

Mistake 3: border and background with Different Shapes

The Problem

When border and background use different shapes (or one has a shape and the other doesn't), the result is a visual mess — the border doesn't follow the background's curves, or the background bleeds outside the border.

// ❌ Background is rectangular, border is rounded — mismatched edges
Box(
    modifier = Modifier
        .fillMaxWidth()
        .background(Color.LightGray)                              // No shape → RECTANGULAR
        .border(2.dp, Color.Red, RoundedCornerShape(12.dp))       // Rounded border
        .padding(16.dp)
)
// Result: Rectangular gray background with a rounded red border overlaid
// The gray corners PEEK OUT beyond the rounded border — ugly

// ❌ Also wrong: border outside padding → gap between border and content
Box(
    modifier = Modifier
        .border(2.dp, Color.Red, RoundedCornerShape(12.dp))
        .padding(16.dp)
        .background(Color.LightGray)
)
// Result: Red border → 16dp gap → gray background → looks like nested boxes

The Fix

// ✅ Same shape for BOTH background and border
val shape = RoundedCornerShape(12.dp)

Box(
    modifier = Modifier
        .fillMaxWidth()
        .background(Color.LightGray, shape)    // Shape matches
        .border(2.dp, Color.Red, shape)         // Same shape
        .padding(16.dp)                         // Content padding INSIDE
) {
    Text("Consistent rounded card")
}
// ✅ Or clip first, then everything inside inherits the shape:
Box(
    modifier = Modifier
        .fillMaxWidth()
        .clip(shape)
        .background(Color.LightGray)
        .border(2.dp, Color.Red, shape)
        .padding(16.dp)
)

Mistake 4: size After fillMaxWidth (Size Gets Silently Ignored)

The Problem

When you chain conflicting size modifiers, the FIRST one wins (outermost layer sets the constraints). The second one is silently ignored — no error, no warning. This is one of the most confusing aspects of Compose for new developers.

// ❌ size(200.dp) is OVERRIDDEN by fillMaxWidth
Box(
    modifier = Modifier
        .size(200.dp)           // 1. OUTER: Set preferred size to 200×200
        .fillMaxWidth()         // 2. INNER: "Fill max width" — but max width is ALREADY 200!
        // Wait — that's NOT what happens. Let me explain...
)
// Actually: Both modifiers add constraints.
// The outer size(200.dp) sets EXACT width = 200, height = 200
// The inner fillMaxWidth() requests max width — which is 200 (already constrained)
// Result: 200 × 200. fillMaxWidth had no visible effect.

// BUT reverse the order:
Box(
    modifier = Modifier
        .fillMaxWidth()         // 1. OUTER: Width = parent's full width (e.g., 1080px)
        .size(200.dp)           // 2. INNER: Wants 200×200 - but width is ALREADY constrained to 1080
        // width stays 1080 (parent), height becomes 200
)
// Result: Full width × 200dp height. NOT 200×200.

The Fix

// ✅ Choose ONE sizing strategy — don't chain conflicting ones
Box(modifier = Modifier.size(200.dp))                          // Fixed 200×200
Box(modifier = Modifier.fillMaxWidth().height(200.dp))         // Full width, 200 height
Box(modifier = Modifier.fillMaxSize())                          // Full parent area
Box(modifier = Modifier.widthIn(min = 100.dp, max = 300.dp))  // Flexible range
Box(modifier = Modifier.width(200.dp).wrapContentHeight())     // Fixed width, flexible height

// RULE: The OUTERMOST size modifier wins.
// If you need both constraints, use a single modifier:
Box(modifier = Modifier.requiredSize(200.dp))  // Ignores parent constraints - FORCES 200×200
// requiredSize is like size() but overrides even parent constraints

Mistake 5: weight() Outside Row/Column Scope

The Problem

Modifier.weight() is a special modifier that ONLY works inside RowScope or ColumnScope. Using it outside these scopes either causes a compilation error (if you're lucky) or does nothing (if you're using it in a way that compiles but doesn't apply).

// ❌ weight() does nothing in Box
Box {
    Text(
        "Hello",
        modifier = Modifier.weight(1f)  // Compilation error: "Unresolved reference"
        // weight() is defined on RowScope.Modifier and ColumnScope.Modifier
        // It doesn't exist on the regular Modifier
    )
}

// ❌ Common mistake: Using weight in a Column where you want fillMaxHeight
Column {
    Text("Header")
    LazyColumn(
        modifier = Modifier
            .fillMaxWidth()
            .weight(1f)  // Takes remaining space after Header - CORRECT usage
    ) { /* ... */ }
    BottomBar()  // Stays at bottom
}
// Without weight(1f), LazyColumn would either:
// - Be 0 height (if no explicit height)
// - Push BottomBar off screen (if fillMaxHeight)

The Fix

// ✅ weight() distributes remaining space proportionally
Row(modifier = Modifier.fillMaxWidth()) {
    // Label takes 30% of width
    Text("Amount:", modifier = Modifier.weight(0.3f))

// Input takes 70% of width
    TextField(
        value = amount,
        onValueChange = { amount = it },
        modifier = Modifier.weight(0.7f)
    )
}
// ✅ Common layout: Header + scrollable content + footer
Column(modifier = Modifier.fillMaxSize()) {
    TopAppBar(title = { Text("Transfers") })        // Fixed height
    LazyColumn(modifier = Modifier.weight(1f)) {     // Takes ALL remaining space
        items(transfers) { TransferRow(it) }
    }
    BottomNavigationBar()                             // Fixed height at bottom
}
// ✅ Equal distribution
Row {
    Box(modifier = Modifier.weight(1f).background(Color.Red))   // 1/3
    Box(modifier = Modifier.weight(1f).background(Color.Green)) // 1/3
    Box(modifier = Modifier.weight(1f).background(Color.Blue))  // 1/3
}

Mistake 6: Not Accepting Modifier Parameter in Custom Composables

The Problem

When you create a custom composable without a modifier parameter, callers can't customize its sizing, padding, click behavior, test tags, or accessibility properties. This breaks composability — the fundamental principle of Compose.

// ❌ RIGID: Caller can't customize anything
@Composable
fun TransferCard(transfer: Transfer) {
    Card(
        modifier = Modifier
            .fillMaxWidth()
            .padding(16.dp)  // Hardcoded — what if caller needs 8dp?
    ) {
        Column(modifier = Modifier.padding(16.dp)) {
            Text(transfer.recipientName, style = MaterialTheme.typography.titleMedium)
            Text("$${transfer.amount}", style = MaterialTheme.typography.bodyLarge)
        }
    }
}

// Caller wants to:
// - Add a testTag for UI testing → can't
// - Change padding for a different layout → can't
// - Add semantics for accessibility → can't
// - Constrain width in a specific layout → can't

The Fix

// ✅ COMPOSABLE: Accept modifier parameter, apply caller's modifier FIRST
@Composable
fun TransferCard(
    transfer: Transfer,
    onClick: () -> Unit,
    modifier: Modifier = Modifier  // Always provide with default Modifier
) {
    Card(
        modifier = modifier          // Caller's modifier FIRST (outermost layer)
            .fillMaxWidth(),          // Then your default sizing
        onClick = onClick
    ) {
        Column(modifier = Modifier.padding(16.dp)) {
            Text(transfer.recipientName, style = MaterialTheme.typography.titleMedium)
            Text("$${transfer.amount}", style = MaterialTheme.typography.bodyLarge)
        }
    }
}

// Now callers can customize EVERYTHING:
TransferCard(
    transfer = transfer,
    onClick = { navigateToDetail(transfer.id) },
    modifier = Modifier
        .padding(horizontal = 8.dp, vertical = 4.dp)  // Custom margin
        .testTag("transfer_card_${transfer.id}")        // Testing
        .semantics { contentDescription = "Transfer to ${transfer.recipientName}" }  // Accessibility
)
// GOOGLE'S CONVENTION (from Material3 source code):
// 1. modifier parameter is ALWAYS the first optional parameter
// 2. Default value is always Modifier (empty)
// 3. Caller's modifier is applied FIRST (outermost)
// 4. Component's own modifiers come after

Mistake 7: Creating Expensive Modifier Chains During Composition

The Problem

Every recomposition creates new objects. If your modifier chain includes expensive computations (reading files, complex calculations, heavy object creation), these run on every recomposition — potentially 60 times per second during animations.

// ❌ New Paint object created on every recomposition
@Composable
fun CustomBackground() {
    val gradient = Brush.linearGradient(  // New Brush on every recomposition
        colors = listOf(Color.Blue, Color.Purple),
        start = Offset(0f, 0f),
        end = Offset(1000f, 1000f)
    )

Box(
        modifier = Modifier
            .fillMaxSize()
            .background(gradient)  // Uses the newly-created gradient
    )
}
// During animation: recomposition happens 60 times/sec
// 60 Brush objects created per second - each allocates native memory

The Fix

// ✅ remember expensive objects
@Composable
fun CustomBackground() {
    val gradient = remember {
        Brush.linearGradient(
            colors = listOf(Color.Blue, Color.Purple),
            start = Offset(0f, 0f),
            end = Offset(1000f, 1000f)
        )
    }  // Created ONCE, reused on every recomposition

Box(modifier = Modifier.fillMaxSize().background(gradient))
}
// ✅ For dynamic values, use remember with keys:
@Composable
fun DynamicBackground(isDarkMode: Boolean) {
    val gradient = remember(isDarkMode) {  // Recreated ONLY when isDarkMode changes
        Brush.linearGradient(
            colors = if (isDarkMode) listOf(Color.DarkGray, Color.Black)
                     else listOf(Color.White, Color.LightGray)
        )
    }
    Box(modifier = Modifier.fillMaxSize().background(gradient))
}
// ✅ For drawBehind / drawWithContent (most efficient):
Box(
    modifier = Modifier
        .fillMaxSize()
        .drawBehind {
            // This lambda is NOT a composable - runs during DRAW phase
            // Creating objects here is fine - it only runs when drawing changes
            drawRect(Brush.linearGradient(listOf(Color.Blue, Color.Purple)))
        }
)

Mistake 8: graphicsLayer After Drawing Modifiers

The Problem

graphicsLayer creates a separate rendering layer that can be transformed (scaled, rotated, translated, faded) independently. If you apply it AFTER drawing modifiers (background, border), the transform may not apply to everything you expect.

// ❌ graphicsLayer only transforms what's INSIDE it (drawn after it in the chain)
Box(
    modifier = Modifier
        .size(100.dp)
        .background(Color.Blue)           // 1. Background drawn at this layer
        .graphicsLayer {
            scaleX = 1.5f; scaleY = 1.5f  // 2. Scale — but background is OUTSIDE this layer!
        }
        .border(2.dp, Color.Red)          // 3. Border drawn INSIDE the scaled layer
)
// Result: Blue background at original size (100dp)
//         Red border scaled up 1.5× (150dp effectively)
//         Border extends BEYOND the blue background — visual mismatch

// ✅ FIX: graphicsLayer BEFORE visual modifiers to transform the whole layer
Box(
    modifier = Modifier
        .size(100.dp)
        .graphicsLayer {
            scaleX = 1.5f; scaleY = 1.5f  // 1. Create a scaled rendering layer
        }
        .background(Color.Blue)           // 2. Background drawn INSIDE the scaled layer
        .border(2.dp, Color.Red)          // 3. Border drawn INSIDE the scaled layer
)
// Result: Both blue background AND red border are scaled 1.5× together
//         Visually consistent - everything scales as a unit

When to Use graphicsLayer

// ✅ Animations (graphicsLayer changes don't trigger recomposition!)
Box(
    modifier = Modifier
        .graphicsLayer {
            alpha = animatedAlpha        // Fade in/out
            translationY = animatedY     // Slide up/down
            rotationZ = animatedRotation // Rotate
            scaleX = animatedScale       // Scale
            scaleY = animatedScale
            // These changes happen in the DRAW phase — no recomposition!
        }
        .background(Color.Blue)
        .padding(16.dp)
)

// ✅ Shadow + elevation
Box(
    modifier = Modifier
        .graphicsLayer {
            shadowElevation = 8.dp.toPx()
            shape = RoundedCornerShape(12.dp)
            clip = true
        }
        .background(Color.White, RoundedCornerShape(12.dp))
)

Mistake 9: Using offset Instead of padding for Layout Spacing

The Problem

offset moves a composable VISUALLY but doesn't change its LAYOUT position. The layout system still thinks the composable is at its original position. This means siblings don't respect the offset — they overlap.

// ❌ offset doesn't affect layout — siblings overlap
Column {
    Text("First Item")
    Text(
        "Second Item",
        modifier = Modifier.offset(y = 32.dp)  // Visually moves down 32dp
    )
    Text("Third Item")  // Positioned as if "Second Item" is still at its original spot
    // Result: "Second Item" OVERLAPS "Third Item"
}

// VISUAL (wrong):
// First Item
//
//
// Second Item    ← Moved down by offset
// Third Item     ← Didn't move! Overlaps with Second Item
// WHAT THE LAYOUT SYSTEM SEES:
// First Item
// Second Item    ← Still at original position in layout
// Third Item     ← Correctly after Second Item

The Fix

// ✅ Use padding for LAYOUT spacing
Column {
    Text("First Item")
    Text(
        "Second Item",
        modifier = Modifier.padding(top = 32.dp)  // Layout knows about this spacing
    )
    Text("Third Item")  // Correctly positioned 32dp below Second Item
}

// ✅ Use Spacer for explicit gaps
Column {
    Text("First Item")
    Spacer(Modifier.height(32.dp))
    Text("Second Item")
    Text("Third Item")
}
// ✅ Use Arrangement for consistent spacing
Column(verticalArrangement = Arrangement.spacedBy(16.dp)) {
    Text("First Item")
    Text("Second Item")
    Text("Third Item")
}
// WHEN TO USE offset:
// ✅ Animations (visual movement without layout recalculation)
// ✅ Overlapping elements on purpose (badges, floating labels)
// ✅ Parallax effects (visual displacement during scroll)
// ❌ NEVER for structural spacing between siblings

The Badge Pattern (Valid offset Use)

// ✅ offset for a notification badge that overlaps its parent
Box {
    IconButton(onClick = { }) {
        Icon(Icons.Default.Notifications, "Notifications")
    }

// Badge overlaps the top-right corner of the icon
    if (unreadCount > 0) {
        Box(
            modifier = Modifier
                .align(Alignment.TopEnd)
                .offset(x = 4.dp, y = (-4).dp)  // Overlap intentionally
                .size(18.dp)
                .background(Color.Red, CircleShape),
            contentAlignment = Alignment.Center
        ) {
            Text("$unreadCount", color = Color.White, fontSize = 10.sp)
        }
    }
}

Mistake 10: Multiple clickable Modifiers (Double Ripple, Double Handler)

The Problem

When a parent AND a child both have clickable, tapping triggers BOTH — you see two ripple effects and both click handlers fire. This happens frequently with Cards that contain clickable elements.

// ❌ Double ripple, double handler
Card(
    modifier = Modifier.clickable { onCardClick() }  // Ripple 1 + handler 1
) {
    Row(
        modifier = Modifier
            .fillMaxWidth()
            .clickable { onCardClick() }  // Ripple 2 + handler 2 (same action!)
            .padding(16.dp)
    ) {
        Text("Transfer to Ahmed")
        Text("$500.00")
    }
}
// Tapping shows TWO expanding ripple circles simultaneously
// onCardClick() is called TWICE
// In a banking app: double-tap prevention logic might fail

// ❌ Also common: Clickable Card with clickable child elements
Card(
    modifier = Modifier.clickable { onCardClick() }
) {
    Row {
        Text("Transfer details")
        IconButton(onClick = { onDeleteClick() }) {  // Tapping icon triggers BOTH
            Icon(Icons.Default.Delete, "Delete")
        }
    }
}
// Tapping the delete icon: onDeleteClick() AND onCardClick() both fire

The Fix

// ✅ FIX 1: Single clickable at the appropriate level
Card(
    onClick = { onCardClick() }  // Card's built-in onClick — single ripple, single handler
) {
    Row(modifier = Modifier.fillMaxWidth().padding(16.dp)) {
        Text("Transfer to Ahmed")
        Text("$500.00")
    }
}

// ✅ FIX 2: Child clickable should stop propagation
Card(
    onClick = { onCardClick() }
) {
    Row(modifier = Modifier.fillMaxWidth().padding(16.dp)) {
        Text("Transfer to Ahmed", modifier = Modifier.weight(1f))
        IconButton(onClick = { onDeleteClick() }) {
            // IconButton handles its own click - Card's onClick is NOT triggered
            // Because IconButton internally uses Modifier.clickable which consumes the pointer event
            Icon(Icons.Default.Delete, "Delete")
        }
    }
}
// ✅ FIX 3: For custom layouts, use pointerInput to stop propagation
Box(
    modifier = Modifier
        .clickable { onOuterClick() }
) {
    Box(
        modifier = Modifier
            .align(Alignment.TopEnd)
            .clickable(
                interactionSource = remember { MutableInteractionSource() },
                indication = rememberRipple(bounded = true)
            ) {
                onInnerClick()
            }
    ) {
        // Inner click area - outer click NOT triggered when tapping here
    }
}

The Golden Rule: Modifier Order = Layer Order

Think of modifiers as WRAPPING LAYERS, applied outside-in:

Modifier
    .padding(16.dp)         // Layer 1 (OUTERMOST): shrink available space by 16dp
    .background(Color.Blue) // Layer 2: paint blue on remaining area
    .clip(RoundedCornerShape(8.dp))  // Layer 3: clip to rounded rect
    .clickable { }          // Layer 4: click target = current (clipped) area
    .padding(8.dp)          // Layer 5 (INNERMOST): shrink content area by 8dp more
Read TOP-TO-BOTTOM = OUTSIDE-TO-INSIDE
Each modifier wraps everything BELOW it
COMMON CORRECT ORDER:
  1. padding (margin - space from siblings)
  2. size / fillMaxWidth (how big)
  3. clip (visual shape)
  4. background / border (visual appearance)
  5. clickable (interaction - bounded to shape)
  6. padding (content padding - space inside)
EXCEPTIONS:
  - graphicsLayer goes BEFORE visual modifiers to transform everything
  - shadow/elevation goes with clip
  - testTag / semantics can go anywhere (don't affect layout)

Quick Reference

MISTAKE                               RULE
──────────────────────────────────────────────────────────────
padding before background          → background FIRST, then padding
clickable before clip              → clip FIRST, then clickable
Mismatched shapes (border/bg)      → Same shape parameter for both
Conflicting sizes                  → Outermost wins; pick ONE strategy
weight outside Row/Column          → Only in RowScope / ColumnScope
No modifier parameter              → Always accept Modifier = Modifier
Expensive objects in modifier      → remember {} or drawBehind {}
graphicsLayer after drawing        → graphicsLayer BEFORE visual modifiers
offset for layout spacing          → Use padding or Spacer instead
Multiple clickable                 → One clickable at the right level

Connect with Me on LinkedIn

Follow me on LinkedIn

Tags: #JetpackCompose #Modifiers #Android #Kotlin #ComposeUI #BestPractices #CommonMistakes #ModifierOrder


메타데이터
post_id
c08fc2c7c858
slug
10-jetpack-compose-modifiers-every-dev-uses-wrong-order-matters-click-areas-break-size-c08fc2c7c858
url
https://medium.com/@ramadan123sayed/10-jetpack-compose-modifiers-every-dev-uses-wrong-order-matters-click-areas-break-size-c08fc2c7c858
canonical_url
https://medium.com/@ramadan123sayed/10-jetpack-compose-modifiers-every-dev-uses-wrong-order-matters-click-areas-break-size-c08fc2c7c858
author_url
https://medium.com/@ramadan123sayed
status
ok
fetched_at
2026-09-10 05:41:31