Flutter Layouts & Responsive Design

Row & Column Fundamentals

50 min Lesson 1 of 16

Introduction to Row & Column

In Flutter, Row and Column are the two most fundamental layout widgets. They arrange their children in a horizontal line (Row) or a vertical line (Column). Nearly every Flutter screen you build will rely on combinations of these two widgets.

Both Row and Column extend the Flex widget under the hood. Row is simply a Flex with direction: Axis.horizontal, and Column is a Flex with direction: Axis.vertical. Understanding this relationship helps you grasp how they calculate sizes and position their children.

Note: Row lays out children left-to-right (or right-to-left in RTL locales). Column lays out children top-to-bottom. Both accept a children parameter that takes a List<Widget>.

The Row Widget

A Row arranges its children horizontally. Each child is placed next to the previous one along the horizontal axis. The Row gives each child as much vertical space as the Row itself has, and each child determines its own width.

Basic Row Example

Row(
  children: <Widget>[
    Icon(Icons.star, color: Colors.amber, size: 32),
    Icon(Icons.star, color: Colors.amber, size: 32),
    Icon(Icons.star, color: Colors.amber, size: 32),
    Text('3 Stars', style: TextStyle(fontSize: 18)),
  ],
)

In this example, three star icons and a text label are arranged side by side. The Row calculates the total width needed by summing up each child’s intrinsic width.

The Column Widget

A Column arranges its children vertically. Each child is stacked below the previous one. The Column gives each child as much horizontal space as the Column itself has, and each child determines its own height.

Basic Column Example

Column(
  children: <Widget>[
    Text('First Item', style: TextStyle(fontSize: 20)),
    Text('Second Item', style: TextStyle(fontSize: 20)),
    Text('Third Item', style: TextStyle(fontSize: 20)),
  ],
)

The children Property

Both Row and Column accept a children property, which is a List<Widget>. You can include any widget in this list -- Text, Icon, Container, Image, or even other Rows and Columns.

Tip: If you have a large or dynamic list of children, consider using ListView instead of Column, or ListView with scrollDirection: Axis.horizontal instead of Row. Row and Column do not scroll and will overflow if their children exceed the available space.

How Row & Column Use Flex

Under the hood, both Row and Column are subclasses of the Flex widget. The only difference is the direction property:

Row and Column as Flex

// Row is equivalent to:
Flex(
  direction: Axis.horizontal,
  children: [/* ... */],
)

// Column is equivalent to:
Flex(
  direction: Axis.vertical,
  children: [/* ... */],
)

This means every property available on Flex -- like mainAxisAlignment, crossAxisAlignment, and mainAxisSize -- is also available on Row and Column. The main axis is the direction of layout (horizontal for Row, vertical for Column), and the cross axis is perpendicular to it.

mainAxisSize: Min vs Max

The mainAxisSize property controls how much space the Row or Column occupies along its main axis:

  • MainAxisSize.max (default) -- The widget takes up all available space along the main axis, even if its children don’t need it.
  • MainAxisSize.min -- The widget shrinks to fit only the space its children need.

mainAxisSize Comparison

// Takes full width of parent
Row(
  mainAxisSize: MainAxisSize.max,
  children: [
    Text('Hello'),
    Text('World'),
  ],
)

// Shrinks to fit content width
Row(
  mainAxisSize: MainAxisSize.min,
  children: [
    Text('Hello'),
    Text('World'),
  ],
)
Note: When using MainAxisSize.min, alignment properties like MainAxisAlignment.spaceBetween have no visible effect because there is no extra space to distribute.

Nesting Row Inside Column and Vice Versa

One of the most powerful patterns in Flutter is nesting layout widgets. You can place a Row inside a Column to create complex grid-like layouts, or nest a Column inside a Row for side-by-side vertical sections.

Row Inside Column

Column(
  children: <Widget>[
    Text('Profile', style: TextStyle(fontSize: 24, fontWeight: FontWeight.bold)),
    SizedBox(height: 16),
    Row(
      children: <Widget>[
        CircleAvatar(radius: 30, child: Icon(Icons.person)),
        SizedBox(width: 12),
        Column(
          crossAxisAlignment: CrossAxisAlignment.start,
          children: <Widget>[
            Text('Ahmed Ali', style: TextStyle(fontSize: 18)),
            Text('Flutter Developer', style: TextStyle(color: Colors.grey)),
          ],
        ),
      ],
    ),
  ],
)

This pattern creates a profile card with a title on top, followed by a row containing an avatar and user details stacked vertically.

Common Mistake: Unbounded Constraints

One of the most frequent errors beginners encounter is placing a Row inside another Row, or a Column inside a scrollable widget, without constraining the child’s size. This causes the unbounded constraints error.

Warning: A Row gives its children unbounded horizontal space to determine their width. If you place another Row (or a widget like ListView) inside a Row without constraining it, Flutter cannot determine how wide the inner widget should be and throws a layout error.

The Problem

// ERROR: Row inside Row without constraints
Row(
  children: [
    Row(
      children: [
        Text('This will cause an error'),
      ],
    ),
  ],
)

// FIX: Wrap inner Row with Expanded or SizedBox
Row(
  children: [
    Expanded(
      child: Row(
        children: [
          Text('This works correctly'),
        ],
      ),
    ),
  ],
)

The same issue occurs when placing a Column inside a Column that is inside a scrollable widget. The inner Column tries to be infinitely tall. The solution is to wrap the inner widget with Expanded, Flexible, or give it a fixed size using SizedBox.

Practical Example: Header Bar

Let’s build a common UI pattern -- a header bar with a back button, title, and action icons:

Header Bar Layout

Container(
  padding: EdgeInsets.symmetric(horizontal: 16, vertical: 12),
  color: Colors.blue,
  child: Row(
    children: <Widget>[
      IconButton(
        icon: Icon(Icons.arrow_back, color: Colors.white),
        onPressed: () {},
      ),
      Expanded(
        child: Text(
          'My App',
          style: TextStyle(
            color: Colors.white,
            fontSize: 20,
            fontWeight: FontWeight.bold,
          ),
          textAlign: TextAlign.center,
        ),
      ),
      IconButton(
        icon: Icon(Icons.settings, color: Colors.white),
        onPressed: () {},
      ),
    ],
  ),
)

The back button and settings icon have fixed sizes, while the title uses Expanded to take up all remaining space and center itself.

Practical Example: Vertical Form Layout

Forms are naturally vertical, making Column the perfect choice:

Form Layout with Column

Padding(
  padding: EdgeInsets.all(16),
  child: Column(
    crossAxisAlignment: CrossAxisAlignment.stretch,
    children: <Widget>[
      Text('Sign Up', style: TextStyle(fontSize: 28, fontWeight: FontWeight.bold)),
      SizedBox(height: 24),
      TextField(decoration: InputDecoration(labelText: 'Full Name', border: OutlineInputBorder())),
      SizedBox(height: 16),
      TextField(decoration: InputDecoration(labelText: 'Email', border: OutlineInputBorder())),
      SizedBox(height: 16),
      TextField(decoration: InputDecoration(labelText: 'Password', border: OutlineInputBorder()), obscureText: true),
      SizedBox(height: 24),
      ElevatedButton(
        onPressed: () {},
        style: ElevatedButton.styleFrom(padding: EdgeInsets.symmetric(vertical: 16)),
        child: Text('Create Account', style: TextStyle(fontSize: 16)),
      ),
    ],
  ),
)

Using CrossAxisAlignment.stretch makes every child take the full width of the Column, which is ideal for form fields and buttons.

Practical Example: Horizontal Button Group

A row of equally-spaced action buttons is another common pattern:

Button Group with Row

Row(
  mainAxisAlignment: MainAxisAlignment.spaceEvenly,
  children: <Widget>[
    Column(
      children: [
        IconButton(icon: Icon(Icons.call, color: Colors.green), onPressed: () {}),
        Text('Call'),
      ],
    ),
    Column(
      children: [
        IconButton(icon: Icon(Icons.message, color: Colors.blue), onPressed: () {}),
        Text('Message'),
      ],
    ),
    Column(
      children: [
        IconButton(icon: Icon(Icons.email, color: Colors.red), onPressed: () {}),
        Text('Email'),
      ],
    ),
  ],
)

Each button is a Column containing an icon and label. The Row distributes them evenly across the available width using MainAxisAlignment.spaceEvenly.

Summary

  • Row arranges children horizontally; Column arranges children vertically.
  • Both extend Flex and share the same alignment and sizing properties.
  • mainAxisSize controls whether the widget expands to fill available space (max) or shrinks to fit its children (min).
  • Nesting Rows and Columns enables complex layouts, but beware of unbounded constraints.
  • Use Expanded or SizedBox to resolve constraint issues when nesting flex widgets.
  • Row and Column do not scroll -- use ListView for scrollable lists.