Jetpack Compose FlexBox: Complete Guide With Examples

TL;DR — Quick Answer Jetpack Compose FlexBox is a new experimental layout API introduced in Compose 1.11.0 that brings CSS-style flex behavior to Android UI. It works by letting child composables grow, shrink, and wrap automatically based on available screen space. Unlike
RowandColumn, it handles multi-line wrapping and adaptive resizing natively — making it the right tool for responsive, adaptive layouts on phones, tablets, and foldables. All configuration lives inside a singleconfiglambda, and item-level behavior is declared viaModifier.flex { }.
If you’ve ever tried to build a layout in Jetpack Compose that needs to wrap its children across multiple lines, or redistribute space dynamically when the screen size changes, you’ve probably hit a wall with Row and Column. They’re great for simple linear flows — but the moment you need wrapping, they break down.
That’s exactly the problem Jetpack Compose FlexBox solves. And if you haven’t tried it yet, this guide will walk you through everything: the correct API surface, container and item properties, real-world adaptive UI patterns, and the gotchas that will catch you on day one.
What Is FlexBox in Jetpack Compose?
Jetpack Compose FlexBox is a composable layout system modeled after the CSS Flexbox specification. Introduced as an experimental API in Compose 1.11.0 (April 2026), it gives Android developers web-style layout power directly inside a Compose hierarchy.
At its core, it organizes child composables along a main axis (horizontal or vertical) while allowing them to wrap onto new flex lines when they run out of space. Each child can be configured to grow to fill available space, shrink when space is tight, or stay fixed — all controlled through a clean, declarative API.
The entire API surface is a single composable — FlexBox — with a config lambda for container behavior and Modifier.flex { } for per-item behavior. There is no FlexRow or FlexColumn shorthand. Direction is set via FlexDirection.Row or FlexDirection.Column inside the config.
If you’ve built web UIs with CSS display: flex, the mental model transfers almost directly. If you haven’t, think of it as a smarter Row that knows what to do when your items don’t fit.
FlexBox vs Row vs Column: What Actually Changed?
Before jumping into the API, it’s worth understanding where FlexBox fits — and when not to reach for it.
| Feature | Row | Column | FlexBox |
|---|---|---|---|
| Direction | Horizontal only | Vertical only | Both (via FlexDirection) |
| Multi-line wrapping | No | No | Yes (FlexWrap.Wrap) |
| Adaptive resizing | Manual | Manual | Built-in (grow(), shrink()) |
| Item alignment | Basic | Basic | Full (FlexAlignItems, FlexAlignSelf) |
| Item ordering control | No | No | Yes (order()) |
| Gap between items | No | No | Yes (gap()) |
| API stability | Stable | Stable | Experimental (1.11.0) |
| Best for | Simple rows | Simple columns | Adaptive, wrapping layouts |
The bottom line: Keep using Row and Column for fixed, predictable layouts. Switch to FlexBox when your UI needs to reflow based on screen size — tag clouds, navigation bars that collapse, card grids that wrap, or any layout that needs to feel equally at home on a small phone and a large tablet.
How to Use FlexBox in Compose: Step-by-Step Setup
Getting FlexBox into your project takes about two minutes. Here’s exactly how to do it.
Step 1 — Update your Compose BOM
// build.gradle.kts (app module)
implementation(platform("androidx.compose:compose-bom:2026.04.01"))This single line pulls in Compose 1.11.0 and all its dependencies at the correct compatible versions.
Step 2 — Add the FlexBox dependency
implementation("androidx.compose.foundation:foundation")FlexBox ships inside compose-foundation — no separate artifact needed.
Step 3 — Opt in to the experimental API
Because FlexBox is currently experimental, you must opt in with @OptIn(ExperimentalFlexBoxApi::class). You can do this per-composable or at the top of the file.
Per-file (recommended when you use FlexBox throughout the file):
@file:OptIn(ExperimentalFlexBoxApi::class)
package com.example.myapp.ui
import androidx.compose.foundation.layout.ExperimentalFlexBoxApi
import androidx.compose.foundation.layout.FlexBox
import androidx.compose.foundation.layout.FlexDirectionPer-composable (recommended for isolated usage):
import androidx.compose.foundation.layout.ExperimentalFlexBoxApi
import androidx.compose.foundation.layout.FlexBox
import androidx.compose.foundation.layout.FlexDirection
@OptIn(ExperimentalFlexBoxApi::class)
@Composable
fun MyFlexScreen() {
FlexBox(
config = {
direction(FlexDirection.Column)
alignItems(FlexAlignItems.Center)
}
) {
Text(text = "Hello", fontSize = 48.sp)
Text(text = "World!", fontSize = 48.sp)
}
}Key insight: The correct annotation is
@OptIn(ExperimentalFlexBoxApi::class)— notExperimentalLayoutApi. Using the wrong annotation may compile but will not properly opt you in to the experimental API contract.
FlexBox Container Properties Explained
All container-level properties are set inside the config = { } lambda. Here is the full set:
direction()
Controls the main axis. Default is FlexDirection.Row.
FlexBox(
config = {
direction(FlexDirection.Column)
}
) {
Text(text = "Hello", fontSize = 48.sp)
Text(text = "World!", fontSize = 48.sp)
}Available values: FlexDirection.Row, FlexDirection.Column, FlexDirection.RowReverse, FlexDirection.ColumnReverse.
wrap()
This is the property Row doesn’t have. Set to FlexWrap.Wrap to allow children to spill onto a new flex line when the container is full.
FlexBox(
config = {
wrap(FlexWrap.Wrap)
gap(8.dp)
}
) {
RedRoundedBox()
BlueRoundedBox()
GreenRoundedBox(modifier = Modifier.flex { grow(1.0f) })
OrangeRoundedBox(modifier = Modifier.flex { grow(1.0f) })
PinkRoundedBox(modifier = Modifier.flex { grow(1.0f) })
}Available values: FlexWrap.Wrap, FlexWrap.NoWrap (default), FlexWrap.WrapReverse.
justifyContent()
Controls how children are distributed along the main axis.
Available values: FlexJustifyContent.Start, FlexJustifyContent.End, FlexJustifyContent.Center, FlexJustifyContent.SpaceBetween, FlexJustifyContent.SpaceAround, FlexJustifyContent.SpaceEvenly.
alignItems()
Controls how children align along the cross axis (perpendicular to the main axis).
Available values: FlexAlignItems.Stretch, FlexAlignItems.Start, FlexAlignItems.End, FlexAlignItems.Center, FlexAlignItems.Baseline.
alignContent()
Controls how flex lines themselves are distributed when there is extra space in the cross axis — only relevant when wrap() is set and multiple lines exist.
Available values: FlexAlignContent.Start, FlexAlignContent.End, FlexAlignContent.Center, FlexAlignContent.SpaceBetween, FlexAlignContent.SpaceAround, FlexAlignContent.Stretch.
gap()
Sets the uniform spacing between all flex items. Takes a single Dp value.
Full container example combining all properties:
FlexBox(
config = {
direction(FlexDirection.Column)
wrap(FlexWrap.Wrap)
alignItems(FlexAlignItems.Center)
alignContent(FlexAlignContent.SpaceAround)
justifyContent(FlexJustifyContent.Center)
gap(16.dp)
}
) {
// child items
}FlexBox Item Properties: Grow, Shrink, Basis, AlignSelf, Order
Individual children declare their flex behavior using Modifier.flex { }. All item properties live inside this lambda.
basis() — the starting size
basis() sets the preferred size of an item before the flex algorithm distributes remaining space. It accepts three forms:
// Auto: uses the item's natural intrinsic size (default)
RedRoundedBox(Modifier.flex { basis(FlexBasis.Auto) })
// Fixed Dp: forces a specific starting size regardless of content
RedRoundedBox(Modifier.flex { basis(200.dp) })
BlueRoundedBox(Modifier.flex { basis(100.dp) })
// Fraction: percentage of the container width (0.0f to 1.0f)
RedRoundedBox(Modifier.flex { basis(0.7f) })
BlueRoundedBox(Modifier.flex { basis(0.3f) })grow() — claiming free space
grow() defines how much of the leftover space an item claims relative to its siblings. A value of 0f means it won’t grow at all. Equal values mean equal distribution.
// Only the red box grows — takes all remaining space after blue and green
FlexBox {
RedRoundedBox(
title = "400dp",
modifier = Modifier.flex { grow(1f) }
)
BlueRoundedBox(title = "100dp")
GreenRoundedBox(title = "100dp")
}When siblings have different grow values, free space is distributed proportionally:
FlexBox {
RedRoundedBox(
title = "150dp",
modifier = Modifier.flex { grow(1f) } // gets 1/6 of free space
)
BlueRoundedBox(
title = "200dp",
modifier = Modifier.flex { grow(2f) } // gets 2/6 of free space
)
GreenRoundedBox(
title = "250dp",
modifier = Modifier.flex { grow(3f) } // gets 3/6 of free space
)
}shrink() — protecting minimum size
shrink() controls how aggressively an item compresses when the container is too narrow. A value of 0f means the item will never shrink below its natural size.
FlexBox {
Text(
"The quick brown fox",
fontSize = 36.sp,
modifier = Modifier
.background(PastelRed)
.flex { shrink(1f) } // will compress if needed
)
Text(
"The quick brown fox",
fontSize = 36.sp,
modifier = Modifier
.background(PastelBlue)
.flex { shrink(0f) } // holds its full width — never shrinks
)
}alignSelf() — per-item cross-axis override
Overrides the container’s alignItems() for a single child only:
FlexBox(
config = {
alignItems(FlexAlignItems.Start)
}
) {
RedRoundedBox() // inherits Start from container
BlueRoundedBox(modifier = Modifier.flex { alignSelf(FlexAlignSelf.Center) })
GreenRoundedBox(modifier = Modifier.flex { alignSelf(FlexAlignSelf.End) })
PinkRoundedBox(modifier = Modifier.flex { alignSelf(FlexAlignSelf.Stretch) })
OrangeRoundedBox(modifier = Modifier.flex { alignSelf(FlexAlignSelf.Baseline) })
}order() — visual reordering without touching your data
order() changes the visual rendering position of an item without changing its position in the composable tree. Lower values render first. Default is 0.
FlexBox {
// Declared first in code — renders second visually (order = 0, default)
RedRoundedBox(title = "World")
// Declared second in code — renders first visually (order = -1)
BlueRoundedBox(
title = "Hello",
modifier = Modifier.flex { order(-1) }
)
}
// Visual output: "Hello" | "World"Key insight:
order()is purely visual. The accessibility tree and keyboard focus order follow declaration order in the composable tree, not the visual order. Only useorder()for decorative reordering — never to resequence interactive or navigable elements.
Combining multiple item properties
FlexBox {
RedRoundedBox(
modifier = Modifier.flex {
basis(FlexBasis.Auto)
grow(1.0f)
shrink(0.5f)
}
)
}3 Real Adaptive UI Patterns Built With FlexBox
This is where the theory pays off. Here are three patterns using the correct Compose FlexBox API — each solves a real adaptive UI problem that Row and Column can’t handle cleanly.
Pattern 1 — Wrapping Tag Cloud
A chip/tag layout that reflows automatically based on available width:
@file:OptIn(ExperimentalFlexBoxApi::class)
import androidx.compose.foundation.layout.ExperimentalFlexBoxApi
import androidx.compose.foundation.layout.FlexBox
import androidx.compose.foundation.layout.FlexWrap
@Composable
fun TagCloud(tags: List<String>) {
FlexBox(
config = {
wrap(FlexWrap.Wrap)
gap(8.dp)
},
modifier = Modifier
.fillMaxWidth()
.padding(16.dp)
) {
tags.forEach { tag ->
Chip(label = tag)
}
}
}On a phone with 8 tags, this wraps onto 3 lines. On a tablet, it fits on one. Zero custom measurement logic — FlexBox handles the reflow entirely.
Pattern 2 — Adaptive Navigation Bar
Navigation items that grow to fill the bar evenly and wrap to a second row on very narrow screens:
@Composable
fun AdaptiveNavBar(items: List<NavItem>) {
FlexBox(
config = {
wrap(FlexWrap.Wrap)
justifyContent(FlexJustifyContent.SpaceEvenly)
alignItems(FlexAlignItems.Center)
},
modifier = Modifier.fillMaxWidth()
) {
items.forEach { item ->
NavBarItem(
item = item,
modifier = Modifier.flex { grow(1f) }
)
}
}
}Each item grows equally to fill available width. No WindowSizeClass checks, no if branches — the layout adapts declaratively.
Pattern 3 — Responsive Card Grid
Cards that start at a minimum width and expand proportionally to fill their row:
@Composable
fun CardGrid(cards: List<CardData>) {
FlexBox(
config = {
wrap(FlexWrap.Wrap)
gap(12.dp)
},
modifier = Modifier
.fillMaxWidth()
.padding(12.dp)
) {
cards.forEach { card ->
ProductCard(
data = card,
modifier = Modifier.flex {
basis(160.dp)
grow(1f)
}
)
}
}
}With basis(160.dp) and grow(1f), each card starts at 160dp and expands to fill the row. On a phone you get 2 per row; on a tablet, 4 or 5 — all without a single breakpoint.
Bonus: Visual Reordering With order()
Use order() to visually prioritize content for different layout contexts without modifying your data model:
@Composable
fun PrioritizedHeroCard(title: String, image: @Composable () -> Unit) {
FlexBox(
config = {
direction(FlexDirection.Column)
alignItems(FlexAlignItems.Center)
}
) {
// Declared first in code, stays first visually
Text(
text = title,
modifier = Modifier.flex { order(0) }
)
// Declared second, but visually rendered above the title
Box(modifier = Modifier.flex { order(-1) }) {
image()
}
}
}Common Mistakes and Gotchas
After testing several screens using the Compose FlexBox layout, here are the errors that catch developers on first contact:
- Wrong
@OptInannotation — use@OptIn(ExperimentalFlexBoxApi::class)fromandroidx.compose.foundation.layout. UsingExperimentalLayoutApiis incorrect. FlexRow/FlexColumndon’t exist — there is onlyFlexBox. Set direction inside the config:config = { direction(FlexDirection.Row) }.- Named parameters on
FlexBox— all container properties (wrap,justifyContent,gap, etc.) live inside theconfiglambda, not as direct composable parameters.FlexBox(flexWrap = ...)will not compile. Modifier.flexItem()doesn’t exist — all item-level behavior is declared viaModifier.flex { grow(...) },Modifier.flex { shrink(...) },Modifier.flex { basis(...) }, etc.- Forgetting
wrap(FlexWrap.Wrap)— the default isNoWrap. Items overflowing off the screen edge are almost always caused by this. It’s the single most common runtime confusion. order()and accessibility —order()changes visual position only. Focus and screen reader traversal follow declaration order. Never use it to resequence interactive elements.- Nesting
FlexBoxinsideLazyColumn—FlexBoxrequires bounded measurement constraints. Placing it inside an unbounded lazy container can cause measurement exceptions. ApplyfillMaxWidth()and test carefully. basis(Float)is a fraction, not a pixel value —basis(0.7f)means 70% of the container, not 0.7dp. Usebasis(160.dp)for absolute sizes.
Frequently Asked Questions
Is FlexBox stable in Jetpack Compose?
Not yet. As of Compose 1.11.0, the FlexBox API is experimental and requires @OptIn(ExperimentalFlexBoxApi::class). Google has signaled it will stabilize in a future release, but the API surface may change before then.
What is the difference between FlexBox and Row in Compose?
Row places children horizontally in a single non-wrapping line. Jetpack Compose FlexBox adds multi-line wrapping (FlexWrap.Wrap), grow/shrink distribution, full alignment control, gap support, and visual item reordering (order()) — making it far more powerful for adaptive layouts.
Do I need to add a separate library dependency for FlexBox?
No. FlexBox ships inside androidx.compose.foundation, which is already part of the standard Compose BOM. Updating to BOM version 2026.04.01 or later is all you need.
Is there a FlexRow or FlexColumn composable?
No. The API exposes a single FlexBox composable. Direction is configured via config = { direction(FlexDirection.Row) } or config = { direction(FlexDirection.Column) }. There are no FlexRow or FlexColumn shorthands.
Can I use FlexBox with Compose Multiplatform?
The API is part of compose-foundation, which is shared in Compose Multiplatform. In practice it should work, but cross-platform testing with foldables and large-screen behaviors is still limited. Test on each target platform separately.
How does FlexBox affect Compose recomposition?
In testing, FlexBox triggers recomposition in line with standard Compose semantics — no additional overhead beyond its layout measurement pass. The SlotTable improvements in Compose 1.11.0 help as composition scales.
Should I replace all my Row/Column layouts with FlexBox?
No. If your layout is simple and linear, Row and Column are faster to write, easier to read, and carry zero experimental risk. Reach for FlexBox specifically when you need wrapping, adaptive resizing, multi-axis alignment, or visual item reordering.
What to Read Next
- Compose adaptive layouts with WindowSizeClass — a practical guide (internal link: your adaptive layout post)
- FlexBox official documentation — Android Developers
- Get started with FlexBox — Android Developers
- FlexBox container behavior — Android Developers
- FlexBox item behavior — Android Developers
- Compose BOM 2026.04.01 release notes
- Understanding Compose layout fundamentals (internal link: your layout basics post)
Final Thoughts
Jetpack Compose FlexBox fills a gap that has frustrated Android developers for years: the lack of a built-in, declarative way to build wrapping, adaptive layouts without custom Layout composables or a maze of WindowSizeClass conditionals.
The API is intentional and clean — one FlexBox composable, a config lambda for the container, and Modifier.flex { } for each item. Once you internalize that mental model, it clicks fast.
It’s experimental today — but the use cases are compelling enough to wire up now. Start with the tag cloud or card grid pattern above, watch your layout adapt across screen sizes without a single breakpoint, and then share what you build.