Skip to content
Sign in

Flutter Layouts Made Easy: Row, Column, Expanded, Flexible and Stack

Understand how Flutter lays out widgets, fix the famous "RenderFlex overflowed" error, and build a real course card step by step using Row, Column, Expanded, Flexible and Stack.

CodeOrbit Learn TeamPublished 4 min read

Most Flutter screens are built from just a handful of layout widgets. Once you understand how Flutter decides sizes, layouts stop feeling like guesswork and the yellow-black "overflow" stripes disappear.

The one rule behind every layout

Keep this sentence in mind; every layout problem below is an example of it.

Row and Column

Row places children horizontally, Column vertically. Two properties control alignment:

  • mainAxisAlignment: along the direction of the widget (horizontal for Row, vertical for Column).
  • crossAxisAlignment: across it.
Dart
Row(
  mainAxisAlignment: MainAxisAlignment.spaceBetween,
  crossAxisAlignment: CrossAxisAlignment.center,
  children: const [
    Icon(Icons.menu_book),
    Text('Chapter 4: Trees'),
    Icon(Icons.chevron_right),
  ],
)

mainAxisAlignment

Effect

start

Children packed at the start

center

Packed in the middle

spaceBetween

First and last at the edges, equal gaps between

spaceAround

Equal space around each child

spaceEvenly

Equal gaps everywhere, including edges

The famous overflow error

Dart
Row(
  children: [
    const Icon(Icons.menu_book),
    Text('Data Structures and Algorithms: complete revision notes for exams'),
  ],
)

On a phone this shows "A RenderFlex overflowed by 87 pixels on the right". Why? A Row gives its children unbounded width, so the Text asks for its full natural width on one line, which is wider than the screen.

The fix is to tell the text how much space it may take, using Expanded:

Dart
Row(
  children: [
    const Icon(Icons.menu_book),
    const SizedBox(width: 8),
    Expanded(
      child: Text(
        'Data Structures and Algorithms: complete revision notes for exams',
        maxLines: 2,
        overflow: TextOverflow.ellipsis,
      ),
    ),
  ],
)

Expanded vs Flexible

Both share the leftover space of a Row or Column by flex factor.

  • Expanded forces the child to fill its share.
  • Flexible lets the child be smaller than its share if it wants.
Dart
Row(
  children: [
    Expanded(flex: 2, child: Container(height: 40, color: Colors.blue)),  // 2/3 of width
    Expanded(flex: 1, child: Container(height: 40, color: Colors.green)), // 1/3 of width
  ],
)

Use Flexible for things like a chip with short text that should not stretch, but must shrink if space runs out.

Stack: put widgets on top of each other

Stack layers children. Use Positioned to pin one to an edge, for example a "FREE" badge on a course image:

Dart
Stack(
  children: [
    ClipRRect(
      borderRadius: BorderRadius.circular(12),
      child: Image.network(course.imageUrl, height: 140, width: double.infinity, fit: BoxFit.cover),
    ),
    Positioned(
      top: 8,
      right: 8,
      child: Container(
        padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 4),
        decoration: BoxDecoration(color: Colors.green, borderRadius: BorderRadius.circular(20)),
        child: const Text('FREE', style: TextStyle(color: Colors.white, fontWeight: FontWeight.bold)),
      ),
    ),
  ],
)

Putting it together: a course card

Dart
class CourseCard extends StatelessWidget {
  const CourseCard({super.key, required this.course});
  final Course course;

  @override
  Widget build(BuildContext context) {
    return Card(
      clipBehavior: Clip.antiAlias,
      child: Column(
        crossAxisAlignment: CrossAxisAlignment.start,
        children: [
          Stack(children: [/* image + FREE badge from above */]),
          Padding(
            padding: const EdgeInsets.all(12),
            child: Column(
              crossAxisAlignment: CrossAxisAlignment.start,
              children: [
                Text(course.title,
                    maxLines: 2,
                    overflow: TextOverflow.ellipsis,
                    style: Theme.of(context).textTheme.titleMedium),
                const SizedBox(height: 8),
                Row(
                  children: [
                    const Icon(Icons.timer_outlined, size: 16),
                    const SizedBox(width: 4),
                    Text('${course.minutes} min'),
                    const Spacer(),                       // pushes the button right
                    FilledButton(onPressed: () {}, child: const Text('Start')),
                  ],
                ),
              ],
            ),
          ),
        ],
      ),
    );
  }
}

Notice Spacer(): it is simply an Expanded with an empty child, a handy way to push widgets apart.

Lists inside Columns

Another classic error: putting a ListView directly inside a Column gives "Vertical viewport was given unbounded height". The Column doesn't limit its children's height, and a list wants to be infinitely tall. Wrap the list in Expanded:

Dart
Column(
  children: [
    const Text('All chapters'),
    Expanded(child: ListView.builder(itemCount: 20, itemBuilder: (_, i) => ListTile(title: Text('Chapter ${i + 1}')))),
  ],
)

Quick debugging checklist

  1. Overflow in a Row? Wrap the long child in Expanded or Flexible.
  2. List in a Column? Wrap the list in Expanded.
  3. Something not centred? Check both main and cross axis alignment.
  4. Turn on Flutter Inspector → Show guidelines to see every box's size.

Tags:#Flutter#Dart#UI

Frequently asked questions

What is the difference between SizedBox and Container?

SizedBox only sets a size (or adds space). Container can also add padding, colour, borders and decoration. Prefer SizedBox for spacing: it is lighter.

When should I use Wrap instead of Row?

Use Wrap when items (like tags or chips) should move to the next line when they run out of space instead of overflowing.

Why does my Column take the full height?

A Column's mainAxisSize is max by default. Set mainAxisSize: MainAxisSize.min to make it only as tall as its children.