What You Will Build

A Pinterest-style masonry layout using LazyVerticalStaggeredGrid where items have varying heights and are arranged in two columns. Each cell displays a colored card with a label and its height value. This layout is essential for image galleries, product catalogs, and social media feeds.

Why This Pattern Matters

Unlike standard grids that enforce uniform cell heights, staggered grids let each item occupy its natural height. Compose provides LazyVerticalStaggeredGrid as a first-class API starting from Compose Foundation 1.3, eliminating the need for third-party libraries.

Step 1: Define the Grid Item

data class StaggeredItem(
    val id: Int,
    val height: Int,
    val color: Color,
    val label: String
)

Step 2: Build the Staggered Grid

@Composable
fun StaggeredGridScreen() {
    val colors = listOf(
        Color(0xFFEF4444), Color(0xFFF59E0B), Color(0xFF10B981),
        Color(0xFF3B82F6), Color(0xFF8B5CF6), Color(0xFFEC4899),
        Color(0xFF14B8A6), Color(0xFFF97316)
    )
    val items = remember {
        (1..20).map { i ->
            StaggeredItem(
                id = i,
                height = Random(i).nextInt(100, 250),
                color = colors[i % colors.size],
                label = "Item $i"
            )
        }
    }

    LazyVerticalStaggeredGrid(
        columns = StaggeredGridCells.Fixed(2),
        modifier = Modifier.fillMaxSize(),
        contentPadding = PaddingValues(12.dp),
        horizontalArrangement = Arrangement.spacedBy(12.dp),
        verticalItemSpacing = 12.dp
    ) {
        items(items, key = { it.id }) { item ->
            Surface(
                modifier = Modifier
                    .fillMaxWidth()
                    .height(item.height.dp),
                shape = RoundedCornerShape(16.dp),
                color = item.color,
                shadowElevation = 4.dp
            ) {
                Box(
                    modifier = Modifier.fillMaxSize().padding(16.dp),
                    contentAlignment = Alignment.BottomStart
                ) {
                    Column {
                        Text(item.label, color = Color.White,
                            fontWeight = FontWeight.Bold, fontSize = 18.sp)
                        Text("${'$'}{item.height}dp",
                            color = Color.White.copy(alpha = 0.7f),
                            fontSize = 12.sp)
                    }
                }
            }
        }
    }
}

Tips and Pitfalls

  • StaggeredGridCells.Fixed(2) creates exactly two columns. Use Adaptive(minSize = 150.dp) for responsive column counts.
  • Provide stable keys via key = { it.id } for efficient recomposition during scrolling.
  • verticalItemSpacing is separate from horizontalArrangement spacing because staggered grids manage vertical gaps per-item.
  • For images: Replace the colored Surface with AsyncImage from Coil and use Modifier.heightIn(min = 100.dp) for natural image sizing.

Get New Tutorials by Email

No spam. Just clear, practical breakdowns you can apply right away.

Enjoy this tutorial?

Get new practical tech tutorials in your inbox.