Skip to content

Flutter Layout — Row, Column, Stack, and Arranging Widgets

DodaTech Updated 2026-06-28 8 min read

In this tutorial, you will learn about Flutter Layout. We cover key concepts, practical examples, and best practices to help you master this topic.

Flutter layout widgets arrange other widgets on screen using rows, columns, stacks, and flexible sizing to create responsive interfaces that adapt to different screen sizes.

What Will You Learn

  • Row and Column for linear layouts
  • MainAxisAlignment and CrossAxisAlignment
  • Expanded and Flexible for proportional sizing
  • Stack and Positioned for overlapping layouts
  • Container for decoration and padding
  • SizedBox and AspectRatio for fixed sizing
  • LayoutBuilder for Responsive Design

Why It Matters

Layout is the foundation of any user interface. Flutter's layout system is based on constraints: parent widgets tell their children how much space they have, and children decide how to use that space. Understanding this constraint-based system is essential for building UIs that look correct on different screen sizes and orientations. Unlike CSS, Flutter layouts are fully type-checked at compile time.

Real-World Use

The DodaTech Flutter app uses Expanded in a Row to create a three-column layout that adapts to screen width. The Stack widget is used for overlaying a play button on top of a course thumbnail image. LayoutBuilder detects the available width and switches between a side-by-side and stacked layout on small screens.

Learning Path

flowchart LR
  A[Flutter Widgets] --> B[Flutter Layout\nYou are here]
  B --> C[Flutter Scrolling]
  style B fill:#f90,color:#fff

Row and Column

Row and Column arrange children horizontally or vertically:

import 'package:flutter/material.dart';

class RowColumnExample extends StatelessWidget {
  const RowColumnExample({super.key});

  @override
  Widget build(BuildContext context) {
    return Column(
      // Vertical arrangement
      mainAxisAlignment: MainAxisAlignment.spaceEvenly,
      // Horizontal alignment of children
      crossAxisAlignment: CrossAxisAlignment.center,
      children: [
        Text('Top', style: TextStyle(fontSize: 24)),
        // Row inside Column
        Row(
          mainAxisAlignment: MainAxisAlignment.spaceAround,
          children: [
            Icon(Icons.star, size: 40, color: Colors.amber),
            Icon(Icons.favorite, size: 40, color: Colors.red),
            Icon(Icons.thumb_up, size: 40, color: Colors.blue),
          ],
        ),
        Text('Bottom', style: TextStyle(fontSize: 24)),
      ],
    );
  }
}

MainAxisAlignment controls spacing along the main axis (vertical for Column, horizontal for Row). CrossAxisAlignment controls alignment on the cross axis. Values include start, center, end, spaceBetween, spaceAround, and spaceEvenly.

Expanded and Flexible

Expanded and Flexible divide available space among children:

import 'package:flutter/material.dart';

class ExpandedExample extends StatelessWidget {
  const ExpandedExample({super.key});

  @override
  Widget build(BuildContext context) {
    return Row(
      children: [
        Expanded(
          flex: 2,
          child: Container(
            color: Colors.red,
            child: Center(child: Text('2/6', style: TextStyle(color: Colors.white))),
          ),
        ),
        Expanded(
          flex: 1,
          child: Container(
            color: Colors.green,
            child: Center(child: Text('1/6', style: TextStyle(color: Colors.white))),
          ),
        ),
        Expanded(
          flex: 3,
          child: Container(
            color: Colors.blue,
            child: Center(child: Text('3/6', style: TextStyle(color: Colors.white))),
          ),
        ),
      ],
    );
  }
}

Expanded forces the child to fill the available space. Flexible allows the child to be smaller than the available space. The flex value determines the proportion of space each child receives.

Stack and Positioned

Stack layers children on top of each other. Positioned places children at specific offsets:

import 'package:flutter/material.dart';

class StackExample extends StatelessWidget {
  const StackExample({super.key});

  @override
  Widget build(BuildContext context) {
    return SizedBox(
      width: 300,
      height: 200,
      child: Stack(
        children: [
          // Base layer
          Container(
            decoration: BoxDecoration(
              borderRadius: BorderRadius.circular(16),
              gradient: LinearGradient(
                colors: [Colors.blue, Colors.purple],
              ),
            ),
          ),
          // Centered text
          Center(
            child: Text(
              'Flutter Stack',
              style: TextStyle(
                color: Colors.white,
                fontSize: 24,
                fontWeight: FontWeight.bold,
              ),
            ),
          ),
          // Positioned badge
          Positioned(
            top: 8,
            right: 8,
            child: Container(
              padding: EdgeInsets.symmetric(horizontal: 8, vertical: 4),
              decoration: BoxDecoration(
                color: Colors.red,
                borderRadius: BorderRadius.circular(12),
              ),
              child: Text(
                'NEW',
                style: TextStyle(
                  color: Colors.white,
                  fontSize: 12,
                  fontWeight: FontWeight.bold,
                ),
              ),
            ),
          ),
          // Positioned at bottom
          Positioned(
            bottom: 8,
            left: 8,
            right: 8,
            child: LinearProgressIndicator(
              value: 0.7,
              backgroundColor: Colors.white24,
              valueColor: AlwaysStoppedAnimation(Colors.green),
            ),
          ),
        ],
      ),
    );
  }
}

The first child in Stack is drawn first. Positioned widgets use top, bottom, left, right offsets from the stack edges. Use Positioned.fill to stretch a child to fill the stack.

Container

Container is a versatile layout widget that combines padding, margins, decoration, and sizing:

Container(
  margin: EdgeInsets.all(16),
  padding: EdgeInsets.symmetric(horizontal: 24, vertical: 12),
  decoration: BoxDecoration(
    color: Colors.white,
    borderRadius: BorderRadius.circular(12),
    boxShadow: [
      BoxShadow(
        color: Colors.black.withOpacity(0.1),
        blurRadius: 8,
        offset: Offset(0, 4),
      ),
    ],
    border: Border.all(
      color: Colors.grey.shade300,
      width: 1,
    ),
  ),
  child: Text('Decorated Container'),
)

Container is convenient but can be overused. For simple padding, use Padding. For sizing, use SizedBox. Container creates a RenderObject even when it does nothing, so prefer specialized widgets for performance.

Alignment and Padding

Control the position and spacing of child widgets:

class AlignmentExample extends StatelessWidget {
  const AlignmentExample({super.key});

  @override
  Widget build(BuildContext context) {
    return Column(
      children: [
        // Align widget
        Align(
          alignment: Alignment.topRight,
          child: Icon(Icons.star, size: 40),
        ),
        SizedBox(height: 16),
        // Padding
        Padding(
          padding: EdgeInsets.only(left: 32, right: 8),
          child: Text('Indented text with left padding'),
        ),
        SizedBox(height: 16),
        // Center
        Center(
          child: Text('Centered text'),
        ),
        SizedBox(height: 16),
        // AspectRatio
        AspectRatio(
          aspectRatio: 16 / 9,
          child: Container(
            color: Colors.blue.shade100,
            child: Center(child: Text('16:9 Box')),
          ),
        ),
      ],
    );
  }
}

Align positions a child within its bounds. Padding adds empty space around a child. Center is shorthand for Align(alignment: Alignment.center). AspectRatio forces a specific width-to-height ratio.

LayoutBuilder

LayoutBuilder adapts the layout based on the available constraints:

class ResponsiveLayout extends StatelessWidget {
  const ResponsiveLayout({super.key});

  @override
  Widget build(BuildContext context) {
    return LayoutBuilder(
      builder: (context, constraints) {
        if (constraints.maxWidth > 600) {
          // Wide layout: side by side
          return Row(
            children: [
              Expanded(
                child: Card(
                  child: Padding(
                    padding: EdgeInsets.all(24),
                    child: Text('Panel 1', style: TextStyle(fontSize: 24)),
                  ),
                ),
              ),
              SizedBox(width: 16),
              Expanded(
                child: Card(
                  child: Padding(
                    padding: EdgeInsets.all(24),
                    child: Text('Panel 2', style: TextStyle(fontSize: 24)),
                  ),
                ),
              ),
            ],
          );
        } else {
          // Narrow layout: stacked vertically
          return Column(
            children: [
              Card(
                child: Padding(
                  padding: EdgeInsets.all(24),
                  child: Text('Panel 1', style: TextStyle(fontSize: 24)),
                ),
              ),
              SizedBox(height: 16),
              Card(
                child: Padding(
                  padding: EdgeInsets.all(24),
                  child: Text('Panel 2', style: TextStyle(fontSize: 24)),
                ),
              ),
            ],
          );
        }
      },
    );
  }
}

LayoutBuilder provides maxWidth and maxHeight from the parent constraints. Use it to switch between different layouts at breakpoints. This is Flutter's equivalent of CSS media queries.

IntrinsicHeight and IntrinsicWidth

These widgets size themselves to their intrinsic content size:

Row(
  children: [
    IntrinsicHeight(
      child: Container(
        color: Colors.blue.shade100,
        padding: EdgeInsets.all(16),
        child: Column(
          children: [
            Text('Short'),
            Text('Content'),
          ],
        ),
      ),
    ),
    SizedBox(width: 8),
    IntrinsicHeight(
      child: Container(
        color: Colors.green.shade100,
        padding: EdgeInsets.all(16),
        child: Column(
          children: [
            Text('Taller'),
            Text('Content'),
            Text('Here'),
            Text('More lines'),
          ],
        ),
      ),
    ),
  ],
)

Without IntrinsicHeight, the two containers would have independent heights. With it, both containers share the height of the tallest child. Use these widgets sparingly as they require two layout passes.

Flex and Wrap

Wrap flows children to the next line when there is not enough space:

class WrapExample extends StatelessWidget {
  const WrapExample({super.key});

  @override
  Widget build(BuildContext context) {
    return Wrap(
      spacing: 8,
      runSpacing: 4,
      alignment: WrapAlignment.center,
      children: [
        Chip(label: Text('Dart')),
        Chip(label: Text('Flutter')),
        Chip(label: Text('Widgets')),
        Chip(label: Text('Layout')),
        Chip(label: Text('State Management')),
        Chip(label: Text('Navigation')),
        Chip(label: Text('Networking')),
        Chip(label: Text('Animations')),
        Chip(label: Text('Testing')),
        Chip(label: Text('Deployment')),
      ],
    );
  }
}

Wrap is ideal for tag clouds, filter chips, and any layout where children may overflow the available width. It automatically wraps to the next line.

Common Mistakes

  1. Using Column inside SingleChildScrollView without Expanded: A Column inside a scroll view does not constrain its height. The column can grow unbounded, causing layout errors. Use Expanded or ConstrainedBox to limit height.

  2. Overusing Container when simpler widgets suffice: Container is heavy. Use SizedBox for fixed sizes, Padding for padding, DecoratedBox for decoration, and ColoredBox for background colors.

  3. Forgetting overflow handling: Overflowing content causes yellow-black striped error indicators in debug mode. Use Flexible, Expanded, or SingleChildScrollView to handle overflow.

  4. Mixing mainAxisAlignment and crossAxisAlignment: The main axis direction depends on the widget (vertical for Column, horizontal for Row). Confusing the two produces unexpected layout.

  5. Not testing on different screen sizes: A layout that works on a Pixel 6 may break on a tablet or small phone. Use LayoutBuilder and test on multiple devices.

Practice Questions

  1. What is the difference between Expanded and Flexible?
  2. How does MainAxisAlignment differ from CrossAxisAlignment?
  3. When would you use Stack instead of Column or Row?
  4. How does LayoutBuilder help build responsive layouts?
  5. Challenge: Build a product card layout with an image on top, title and description in the middle, and a price and button row at the bottom. Use Stack for a discount badge overlay. Make it responsive to screen width.

Mini Project

Build a responsive dashboard layout:

  • Header with app title and user avatar
  • Two-panel layout: sidebar navigation and main content
  • On narrow screens (width < 600), hide sidebar and show drawer
  • Use LayoutBuilder to switch between layouts
  • Use Stack for overlapping profile image
  • Use Expanded for proportional panel sizing

FAQ

Why is my Column overflowing?

The Column is inside a container that provides unbounded height (like a scroll view or another Column). Wrap the Column in Expanded or ConstrainedBox with a max height.

What is the difference between EdgeInsets and padding?

EdgeInsets is the class that specifies margins and padding amounts. Padding is the widget that applies it. They work together.

How do I center a widget both horizontally and vertically?

Wrap it in Center widget, or use Align(alignment: Alignment.center, child: ...).

What does `MainAxisSize.min` do?

A Row or Column with MainAxisSize.min shrinks to fit its children instead of filling the available space. Default is MainAxisSize.max.

How do I create equal-height columns?

Use IntrinsicHeight around the Row, or use a Row with CrossAxisAlignment.stretch inside a fixed-height container.

What is Next

Now that you understand layout, learn about scrolling widgets. Proceed to Flutter Scrolling for ListView, GridView, and scrollable content. Then explore State Management in Flutter for managing app state.

Built by the developers of DodaTech

Doda Browser, DodaZIP & Durga Antivirus Pro