Skip to content
FlutterLearn

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.

  1. A parent passes constraints to its child — a minimum and maximum width and height.
  2. The child picks its own size within those constraints and reports it back.
  3. 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.

Dart
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'),
  ],
)
PropertyControlsCommon values
mainAxisAlignmentDistribution along the primary axisstart, center, spaceBetween, spaceEvenly
crossAxisAlignmentAlignment on the other axisstart, center, stretch
mainAxisSizeWhether to fill or shrink-wrap the main axismax (default), min
spacingUniform gap between childrenany 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.

Dart
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:

SituationFix
Long text in a RowWrap the Text in Expanded (it will then wrap or ellipsise)
Column taller than the screenWrap the Column in SingleChildScrollView, or use ListView
Row of chips that runs off screenUse Wrap instead of Row so items flow onto new lines
Dart
// 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.

Dart
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

Dart
// 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.
  • Expanded fills leftover space in a Row/Column; flex divides it proportionally.
  • Overflow means a child exceeded its constraints — scroll it, wrap it, or Expand it.
  • Stack + Positioned handles overlap; Wrap handles 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
  • Stack with clipBehavior: Clip.none lets the avatar hang outside the bounds.
  • Wrap the name text in Expanded so long names ellipsise instead of overflowing.