Flutter Row and Column: MainAxis, CrossAxis, and Spacing Explained

Coding Liquids blog cover featuring Sagnik Bhattacharya for the Flutter Row and Column guide, with main axis and cross axis alignment, Expanded and Flexible, and spacing visuals.
Coding Liquids blog cover featuring Sagnik Bhattacharya for the Flutter Row and Column guide.

Row and Column are the workhorses of Flutter layout. Almost every screen is, at heart, columns of rows: a header row, a content column, a button row. They are also where beginners meet their first confusing error — RenderFlex overflowed — and their first "why won't this centre?" moment. Once you understand the main axis, the cross axis, and how Expanded shares space, both widgets become predictable. This guide makes that click, with runnable code for every concept.

The Complete Flutter Guide course thumbnail

The Complete Flutter Guide: Build Android, iOS and Web apps

Go from scratch to building industry-standard apps with Riverpod, Firebase, animations, REST APIs, and more.

Enrol now

Every snippet is paste-ready against current stable Flutter. Row and Column share almost all of their properties — they are both Flex widgets, just with different directions — so learning one teaches you the other. For the bigger layout picture, this pairs with the Flutter Layout Widgets guide.

Follow me on Instagram@sagnikteaches

We will start with the axis model, then cover the two alignment properties, MainAxisSize, sharing space with Expanded, Flexible, and Spacer, adding spacing, and finally the overflow error and how to fix it for good.

Connect on LinkedInSagnik Bhattacharya

This tutorial is part of a series on Flutter's core widgets. Its closest companions are the Container guide and the Stack and Positioned guide — the boxes you place inside these rows and columns, and the way to layer them.

Subscribe on YouTube@codingliquids

The main axis and the cross axis

Every Row and Column has two axes. The main axis is the direction it lays children out; the cross axis is perpendicular to it.

  • Row → main axis is horizontal, cross axis is vertical.
  • Column → main axis is vertical, cross axis is horizontal.

Almost every alignment question comes down to "which axis am I trying to move things on?" Get that right and the property name follows: mainAxisAlignment moves children along the layout direction, crossAxisAlignment moves them across it.

Row(
  children: const [
    Icon(Icons.star),
    Text('Rating'),
    Icon(Icons.chevron_right),
  ],
)

Column(
  children: const [
    Text('Title', style: TextStyle(fontSize: 22)),
    Text('Subtitle'),
  ],
)

MainAxisAlignment: positioning along the main axis

mainAxisAlignment decides how children are distributed along the main axis when there is spare room. In a Row that is left-to-right; in a Column, top-to-bottom.

Row(
  mainAxisAlignment: MainAxisAlignment.spaceBetween,
  children: const [Text('Left'), Text('Middle'), Text('Right')],
)

The full set:

  • start (default) — pack at the beginning.
  • end — pack at the end.
  • center — pack in the middle.
  • spaceBetween — first and last flush to the edges, equal gaps between.
  • spaceAround — equal gaps around each child (half-size gaps at the ends).
  • spaceEvenly — equal gaps everywhere, including the ends.

CrossAxisAlignment: positioning across the main axis

crossAxisAlignment positions children on the other axis. In a Column this controls horizontal alignment of the children; stretch is the one beginners reach for to make children fill the width.

Column(
  crossAxisAlignment: CrossAxisAlignment.stretch,
  children: [
    Container(height: 40, color: Colors.amber.shade200),
    const SizedBox(height: 8),
    Container(height: 40, color: Colors.teal.shade200),
  ],
)

The values are start, end, center (default), stretch (fill the cross axis), and baseline (align text baselines — requires a textBaseline). Note stretch needs a bounded cross axis; inside an unbounded parent it will complain.

The Complete Flutter Guide course thumbnail

Lay out real screens with confidence

The Complete Flutter Guide teaches the layout system end to end, so rows and columns stop fighting you.

Enrol now

MainAxisSize: how big is the Row or Column itself?

By default a Row or Column tries to be as big as possible along its main axis (MainAxisSize.max). Set mainAxisSize: MainAxisSize.min to make it shrink to wrap its children — essential when you put a Row inside something that should hug its content, like a button or a chip.

Row(
  mainAxisSize: MainAxisSize.min, // wrap content instead of filling the width
  children: const [Icon(Icons.check), SizedBox(width: 6), Text('Done')],
)

Sharing space: Expanded, Flexible, and Spacer

This is the heart of flex layout. When children should divide the main-axis space between them, wrap them in Expanded or Flexible. Each takes a flex factor (default 1) that sets its share.

Row(
  children: [
    Expanded(flex: 2, child: Container(height: 40, color: Colors.amber)),
    Expanded(flex: 1, child: Container(height: 40, color: Colors.teal)),
  ],
)

The amber box takes two-thirds, the teal box one-third. The difference between the two wrappers is about fit:

  • Expanded forces the child to fill its share (it is Flexible with fit: FlexFit.tight).
  • Flexible lets the child take up to its share but stay smaller if it wants (fit: FlexFit.loose).
  • Spacer is an Expanded with an empty child — a flexible gap that pushes siblings apart.
Row(
  children: const [
    Text('Title'),
    Spacer(),               // pushes the next child to the far end
    Icon(Icons.more_vert),
  ],
)

Adding spacing between children

There are three clean ways to put gaps between children. Use MainAxisAlignment values like spaceBetween to distribute, insert SizedBox widgets for fixed gaps, or — in current Flutter — pass the spacing argument to Row or Column for an even gap without manual SizedBoxes.

// Fixed gaps with SizedBox
Column(
  children: const [
    Text('One'),
    SizedBox(height: 12),
    Text('Two'),
  ],
)

// Even gaps with the spacing argument
Column(
  spacing: 12,
  children: const [Text('One'), Text('Two'), Text('Three')],
)

The RenderFlex overflow error

The famous yellow-and-black stripes appear when children are bigger than the main-axis space — a long Text in a Row is the classic culprit. Because a Row gives its children unbounded width, the text lays out at full length and pushes past the edge. The fix is always to control how main-axis space is shared:

  • Wrap the flexible child in Expanded or Flexible so it shrinks to fit (text will then wrap or ellipsis).
  • Let the text wrap or truncate with overflow: TextOverflow.ellipsis once it is inside Expanded.
  • Make the Row scroll with a horizontal ListView or SingleChildScrollView if the content genuinely needs more room.
Row(
  children: [
    const Icon(Icons.message),
    const SizedBox(width: 8),
    Expanded(
      child: Text(
        'A very long message that would otherwise overflow the row',
        overflow: TextOverflow.ellipsis,
      ),
    ),
  ],
)

This error is common enough to deserve its own deep dive — see How to Fix RenderFlex Overflowed in Flutter Row inside ListView for every variation and fix.

Common Row and Column mistakes

  • Putting an unbounded child in the wrong axis. A Column inside a Column, or a ListView in a Column, needs Expanded or a bounded height.
  • Using crossAxisAlignment.stretch with no bounded cross axis. Give the parent a width (for a Column) so there is something to stretch into.
  • Reaching for Spacer when spaceBetween is clearer. For evenly distributed children, MainAxisAlignment reads better.
  • Forgetting MainAxisSize.min. A Row in a button or dialog often needs to wrap its content rather than fill the width.
  • Nesting deep Row/Column trees. Beyond a few levels, a Wrap, GridView, or Table is usually cleaner.

Frequently asked questions

What is the difference between the main axis and the cross axis?

The main axis is the layout direction; the cross axis is perpendicular. In a Row the main axis is horizontal; in a Column it is vertical. mainAxisAlignment positions children along the main axis, crossAxisAlignment across it.

What is the difference between Expanded and Flexible?

Both share main-axis space. Expanded forces the child to fill its share (tight fit); Flexible lets the child take up to its share but stay smaller (loose fit by default).

How do I fix a RenderFlex overflow in a Row?

Wrap the flexible child (usually text) in Expanded or Flexible so it shrinks, add overflow: TextOverflow.ellipsis, or make the Row scroll. Overflow only happens on the main axis.

How do I add spacing between children?

Use MainAxisAlignment values like spaceBetween, insert SizedBoxes, or pass the spacing argument to the Row or Column for an even gap.

Further reads

Keep going with the tutorials that pair with this guide:

Sources: Flutter documentation — Row, Column, Flex, Expanded, Flexible API references, plus the Layout and "Understanding constraints" guides (docs.flutter.dev). Verified against current stable Flutter.