Beginner · Lesson 4 · 13 min read
Layout: Row, Column, Stack and Constraints
Master Flutter's layout model — constraints go down, sizes go up — and stop fighting overflow errors.
Updated July 20, 2026
What you will learn
- Explain Flutter's constraint-based layout algorithm
- Lay out UI with Row, Column, Expanded and Flexible
- Overlap widgets with Stack and Positioned
- Diagnose and fix the yellow-and-black overflow stripes
Almost every layout frustration in Flutter comes from not internalising one sentence: constraints go down, sizes go up, the parent sets the position.
- A parent passes constraints to its child — a minimum and maximum width and height.
- The child picks its own size within those constraints and reports it back.
- The parent decides where to place the child.
A widget can never know its own position, and it can never be larger than its constraints allow. When a layout does something unexpected, ask: what constraints is the parent giving, and are they tight or loose?
Column and Row
Column lays children out vertically, Row horizontally. They share the same API — only the axis differs.
Column(
// Along the main axis (vertical for Column)
mainAxisAlignment: MainAxisAlignment.spaceBetween,
// Across the cross axis (horizontal for Column)
crossAxisAlignment: CrossAxisAlignment.start,
// Shrink to fit children instead of filling the parent
mainAxisSize: MainAxisSize.min,
children: const [
Text('Top'),
Text('Middle'),
Text('Bottom'),
],
)| Property | Controls | Common values |
|---|---|---|
mainAxisAlignment | Distribution along the primary axis | start, center, spaceBetween, spaceEvenly |
crossAxisAlignment | Alignment on the other axis | start, center, stretch |
mainAxisSize | Whether to fill or shrink-wrap the main axis | max (default), min |
spacing | Uniform gap between children | any double (Flutter 3.27+) |
Expanded and Flexible
Children of a Row or Column are laid out at their natural size by default. To make one take the leftover space, wrap it in Expanded.
Row(
children: [
const Icon(Icons.search),
const SizedBox(width: 12),
// Takes all remaining horizontal space
const Expanded(
child: TextField(
decoration: InputDecoration(hintText: 'Search'),
),
),
TextButton(onPressed: () {}, child: const Text('Go')),
],
)With multiple Expanded children, the flex factor splits the space proportionally. flex: 2 next to flex: 1 gives a two-to-one split.
Flexible is the softer version: it allows the child to be smaller than the allotted space, whereas Expanded forces it to fill. Expanded is literally Flexible(fit: FlexFit.tight).
Fixing overflow
The yellow-and-black striped bar means a child asked for more space than its parent allows. The three usual causes and their fixes:
| Situation | Fix |
|---|---|
| Long text in a Row | Wrap the Text in Expanded (it will then wrap or ellipsise) |
| Column taller than the screen | Wrap the Column in SingleChildScrollView, or use ListView |
| Row of chips that runs off screen | Use Wrap instead of Row so items flow onto new lines |
// Text overflowing a Row
Row(
children: [
const Icon(Icons.person),
Expanded(
child: Text(
'A very long user name that will not fit on one line',
overflow: TextOverflow.ellipsis,
maxLines: 1,
),
),
],
)Stack for overlapping UI
Stack paints children on top of one another, in list order. Use Positioned to anchor a child to specific edges.
Stack(
children: [
Image.network(imageUrl, fit: BoxFit.cover),
// Gradient scrim so text stays readable over any image
Positioned.fill(
child: DecoratedBox(
decoration: BoxDecoration(
gradient: LinearGradient(
begin: Alignment.topCenter,
end: Alignment.bottomCenter,
colors: [Colors.transparent, Colors.black.withValues(alpha: 0.7)],
),
),
),
),
const Positioned(
left: 16,
bottom: 16,
child: Text(
'Sunset over Karachi',
style: TextStyle(color: Colors.white, fontSize: 20),
),
),
],
)Spacing, padding and sizing
// Padding around a child
const Padding(
padding: EdgeInsets.symmetric(horizontal: 16, vertical: 8),
child: Text('Padded'),
)
// Fixed-size box, or a pure spacer
const SizedBox(height: 24)
const SizedBox(width: 120, height: 40, child: ColoredBox(color: Colors.red))
// Constrain a child's maximum width — useful on tablets
ConstrainedBox(
constraints: const BoxConstraints(maxWidth: 480),
child: const Card(child: Text('Never wider than 480')),
)
// Keep aspect ratio regardless of available width
const AspectRatio(aspectRatio: 16 / 9, child: Placeholder())Key takeaways
- Constraints go down, sizes go up, parents position children — reason in that order.
Expandedfills leftover space in a Row/Column;flexdivides it proportionally.- Overflow means a child exceeded its constraints — scroll it, wrap it, or Expand it.
Stack+Positionedhandles overlap;Wraphandles items that need to flow onto new lines.
Practice
Recreate a profile header
Build a header with a background image, a dark gradient scrim, a circular avatar overlapping the bottom edge, and a name plus subtitle to the right of the avatar. It must not overflow on a narrow phone in landscape.
Show hints
StackwithclipBehavior: Clip.nonelets the avatar hang outside the bounds.- Wrap the name text in
Expandedso long names ellipsise instead of overflowing.