Almost every layout you will ever build in Flutter is made from a small set of layout widgets arranged in different ways. Once you understand Container, Row, Column, Stack, Expanded, and a handful of others — and the single rule that governs how they size each other — building UI stops feeling like guesswork. This guide walks through every layout widget that matters, with runnable code for each one, and the mental model that ties them together.

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 nowThis is a reference you can come back to. It is organised the same way Flutter organises layout itself: first the constraints rule, then single-child layout widgets, then multi-child layout widgets, then the responsive helpers, and finally a complete worked screen that combines them. If you are brand new to Flutter, read it top to bottom once; if you are already shipping apps, jump to the widget you need.
Every code block here is a complete, paste-ready widget. Drop any of them into a Scaffold body in a fresh Flutter project to see the result. The examples target a current stable Flutter and Material 3, but the layout widgets themselves have been stable for years, so the concepts apply whatever version you are on.
One more thing before we start: if you keep hitting the yellow-and-black overflow stripe, that is a layout-constraints problem, and this guide explains exactly why it happens. There is also a dedicated deep dive on fixing RenderFlex overflow linked at the end.
The one rule that explains all Flutter layout
Before any specific widget, learn this sentence, because it explains every layout you will ever debug:
Constraints go down. Sizes go up. The parent sets the position.
- Constraints go down. A parent widget gives each child a
BoxConstraints— a minimum width, maximum width, minimum height, and maximum height. - Sizes go up. Each child picks its own size within those constraints and reports that size back to the parent.
- The parent sets the position. The parent decides where, relative to itself, to place the child.
Constraints come in three flavours, and most confusion disappears once you can tell them apart:
- Tight constraints force an exact size (min equals max). A child given tight constraints has no choice — it becomes that size.
- Loose constraints set a maximum but a minimum of zero. The child can be any size up to the maximum.
- Unbounded constraints have an infinite maximum. This is what scrollable widgets give their children along the scroll axis, and it is the source of most "unbounded height/width" errors.
Quick examples of the rule in action: a Center receives the screen size, loosens the constraints, lets its child be its natural size, and positions it in the middle. A bare Container with no child and no size expands to fill its parent. The same Container with a child shrinks to fit the child. None of that is magic — it is just the rule applied.
Single-child layout widgets
Single-child layout widgets wrap exactly one child and change how it is sized, positioned, padded, or decorated. These are the building blocks you reach for constantly.
Container — the all-in-one box
Container is the Swiss-army widget. It composes padding, margin, alignment, constraints, and a BoxDecoration into one widget. Reach for it when you want a styled box.
Container(
width: 200,
height: 120,
margin: const EdgeInsets.all(16),
padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 12),
alignment: Alignment.centerLeft,
decoration: BoxDecoration(
color: Colors.deepPurple.shade50,
borderRadius: BorderRadius.circular(16),
border: Border.all(color: Colors.deepPurple.shade200),
boxShadow: const [
BoxShadow(color: Colors.black12, blurRadius: 8, offset: Offset(0, 4)),
],
),
child: const Text('A styled Container'),
)
Two things trip people up. First, you cannot set both color and decoration at once — put the colour inside the BoxDecoration. Second, an empty Container() with no width, height, child, or constraints will expand to fill its parent if the parent allows it, but collapse to zero if the parent gives unbounded constraints. If you only need a fixed size or a gap, prefer SizedBox — it is lighter and can be const.
Padding — inset the child
Padding does one job and does it cheaply: it insets its child by an EdgeInsets. Because it is single-purpose, it is clearer than a Container when padding is all you need.
Padding(
padding: const EdgeInsets.all(24),
child: Text('I have 24 logical pixels of breathing room on every side'),
)
The EdgeInsets constructors are worth memorising: EdgeInsets.all(8), EdgeInsets.symmetric(horizontal: 16, vertical: 8), EdgeInsets.only(left: 12, top: 4), and EdgeInsets.fromLTRB(8, 4, 8, 12). For layouts that must mirror in right-to-left languages, use EdgeInsetsDirectional.only(start: 12) instead of left.
Center and Align — position within available space
Center places its child in the middle of the space it is given. Align is the general form: it places the child at any Alignment, where (-1, -1) is top-left, (0, 0) is centre, and (1, 1) is bottom-right.
Align(
alignment: Alignment.bottomRight,
child: FloatingActionButton(
onPressed: () {},
child: const Icon(Icons.add),
),
)
Both widgets try to be as large as possible by default (so they can position the child anywhere), then size the child loosely. If you wrap a small child in Center and nothing seems centred, check whether the Center itself is constrained to the child's size by a parent.
SizedBox — fixed size and spacing
SizedBox forces a specific width and/or height onto its child, or — with no child — acts as a fixed gap. It is the idiomatic way to space widgets apart inside a Row or Column.
Column(
children: const [
Text('Title'),
SizedBox(height: 8), // vertical gap
Text('Subtitle'),
SizedBox(height: 24),
Text('Body text down here'),
],
)
Handy variants: SizedBox.expand fills its parent, SizedBox.shrink takes the least space possible, and SizedBox.square(dimension: 48) makes an equal-sided box. Because SizedBox is so cheap, use it for gaps rather than wrapping things in padded Containers.

Build real Flutter apps, not just demos
The Complete Flutter Guide takes you from widgets like these to shipping Android, iOS and web apps with state management, Firebase, and animations.
Enrol nowConstrainedBox, UnconstrainedBox, and LimitedBox
ConstrainedBox adds extra constraints on top of what the parent already imposed. It is the right tool when a child should never grow past a maximum or shrink below a minimum.
ConstrainedBox(
constraints: const BoxConstraints(
minWidth: 120,
maxWidth: 320,
minHeight: 48,
),
child: const Text('I stay between 120 and 320 px wide'),
)
UnconstrainedBox does the opposite: it lets its child be its natural size even if the parent tried to constrain it (useful, but a common cause of overflow if the child is bigger than the screen). LimitedBox only applies its limits when the incoming constraint is unbounded — it is mainly used to give a sensible maximum to children of scrolling widgets.
AspectRatio, FractionallySizedBox, and FittedBox
AspectRatio sizes its child to a width-to-height ratio. Give it an aspectRatio of 16 / 9 and it will pick the largest size with that ratio that fits the constraints.
AspectRatio(
aspectRatio: 16 / 9,
child: Container(color: Colors.black12, child: const Center(child: Text('16:9'))),
)
FractionallySizedBox sizes the child as a fraction of the available space — widthFactor: 0.8 makes the child 80% as wide as its parent. FittedBox scales and positions its child to fit inside the available space, which is the simplest way to stop a long line of text or a large logo from overflowing:
FittedBox(
fit: BoxFit.scaleDown,
child: Text('This text shrinks instead of overflowing'),
)
IntrinsicHeight, IntrinsicWidth, and SafeArea
IntrinsicHeight and IntrinsicWidth force children to share a common height or width based on their natural sizes — for example, making two cards in a Row equal height regardless of content. They are convenient but relatively expensive, so use them only where a cheaper layout will not do.
IntrinsicHeight(
child: Row(
crossAxisAlignment: CrossAxisAlignment.stretch,
children: const [
Expanded(child: Card(child: Padding(padding: EdgeInsets.all(12), child: Text('Short')))),
Expanded(child: Card(child: Padding(padding: EdgeInsets.all(12), child: Text('Much longer text that forces a taller card and now both match')))),
],
),
)
SafeArea insets its child away from notches, status bars, and rounded screen corners. Wrap the top-level content of a screen in SafeArea so nothing important hides under the system UI.
Multi-child layout widgets
Multi-child layout widgets arrange a list of children. These are where most real screens are built.
Row and Column — the workhorses
Row lays children out horizontally; Column lays them out vertically. Both are Flex widgets, so they share the same alignment properties. The two you will set most often are mainAxisAlignment (along the layout direction) and crossAxisAlignment (perpendicular to it).
Column(
mainAxisAlignment: MainAxisAlignment.center, // vertical: centre the group
crossAxisAlignment: CrossAxisAlignment.start, // horizontal: left-align each child
children: const [
Text('Heading', style: TextStyle(fontSize: 22, fontWeight: FontWeight.bold)),
SizedBox(height: 8),
Text('A subtitle on the next line'),
],
)
Key options for mainAxisAlignment are start, center, end, spaceBetween, spaceAround, and spaceEvenly. For crossAxisAlignment they are start, center, end, and stretch. Set mainAxisSize: MainAxisSize.min when you want the Row or Column to be only as big as its children rather than filling the axis.
Expanded, Flexible, and Spacer — sharing space
Inside a Row or Column, Expanded and Flexible divide the leftover space among children. Expanded forces its child to fill its share; Flexible lets the child be smaller than its share. Both accept a flex factor to control the ratio.
Row(
children: [
Expanded(flex: 2, child: Container(height: 60, color: Colors.red)),
Expanded(flex: 1, child: Container(height: 60, color: Colors.green)),
const SizedBox(width: 8),
Flexible(child: Container(height: 60, width: 200, color: Colors.blue)),
],
)
Here the red box takes two-thirds and the green box one-third of the flexible space, while the blue box (wrapped in Flexible) shrinks if there is not enough room. Spacer is a shorthand for an Expanded empty gap — perfect for pushing one child to the far end of a Row:
Row(
children: const [
Text('Left'),
Spacer(), // eats all remaining space
Text('Right'),
],
)
If you see "RenderFlex overflowed", it almost always means a child of a Row or Column wanted more space than was available and was not wrapped in Expanded or Flexible. The RenderFlex overflow deep dive covers every fix in detail.
Stack and Positioned — layering widgets
Stack places children on top of one another, painting later children above earlier ones. Combine it with Positioned to pin a child to an edge or corner — ideal for badges, overlays, and floating controls.
Stack(
children: [
Container(height: 160, width: double.infinity, color: Colors.indigo),
const Positioned(
bottom: 12,
left: 16,
child: Text('Caption over the image',
style: TextStyle(color: Colors.white, fontSize: 18)),
),
Positioned(
top: 8,
right: 8,
child: Container(
padding: const EdgeInsets.all(6),
decoration: const BoxDecoration(color: Colors.amber, shape: BoxShape.circle),
child: const Text('3'),
),
),
],
)
Non-positioned children in a Stack are aligned by the stack's alignment property (default top-left). Use the fit property and StackFit.expand when you want non-positioned children to fill the stack.

Go from widgets to a finished app
Layout is step one. The Complete Flutter Guide shows you how to wire these widgets into real, production-ready Android, iOS and web apps.
Enrol nowWrap — rows and columns that break onto new lines
A Row overflows when its children do not fit. Wrap instead moves overflowing children onto the next line (or column). It is the right tool for tag lists, chip groups, and filter pills.
Wrap(
spacing: 8, // gap between chips on a line
runSpacing: 8, // gap between lines
children: const [
Chip(label: Text('Flutter')),
Chip(label: Text('Dart')),
Chip(label: Text('Layout')),
Chip(label: Text('Widgets')),
Chip(label: Text('Cross-platform')),
],
)
Table and the scrolling widgets
Table lays children out in a grid of rows and columns with shared column widths — useful for genuinely tabular data where columns must align. For long or dynamic lists, you will use the scrolling widgets: ListView, GridView, SingleChildScrollView, and the slivers. They deserve their own treatment because they introduce unbounded constraints along the scroll axis, which is a whole category of layout behaviour. See the dedicated scrolling guide linked under Further reads.
// A simple, fixed list. For long or dynamic lists use ListView.builder.
ListView(
children: const [
ListTile(leading: Icon(Icons.home), title: Text('Home')),
ListTile(leading: Icon(Icons.search), title: Text('Search')),
ListTile(leading: Icon(Icons.settings), title: Text('Settings')),
],
)
Responsive layout helpers
Three widgets let your layout react to the space and device it is running on, which is essential when the same Flutter app ships to phones, tablets, desktop, and web.
LayoutBuilder — build based on the constraints you receive
LayoutBuilder gives you the BoxConstraints from the parent at build time, so you can choose a layout based on the available width rather than the whole screen size.
LayoutBuilder(
builder: (context, constraints) {
if (constraints.maxWidth >= 600) {
// Wide: two columns side by side
return Row(
children: const [
Expanded(child: _Sidebar()),
Expanded(flex: 2, child: _Content()),
],
);
}
// Narrow: stack them
return Column(children: const [_Content(), _Sidebar()]);
},
)
MediaQuery and OrientationBuilder
MediaQuery.of(context) exposes the screen size, padding (notches), text scale factor, and platform brightness. Use MediaQuery.sizeOf(context) to read just the size efficiently. OrientationBuilder rebuilds when the device rotates so you can switch between portrait and landscape layouts. For a full treatment of multi-device design, see the responsive UI guide under Further reads.
final width = MediaQuery.sizeOf(context).width;
final columns = width >= 900 ? 4 : width >= 600 ? 3 : 2;
// feed columns into a GridView.count(crossAxisCount: columns, ...)
A complete worked example: a profile card
Here is a single screen that combines many of the widgets above — SafeArea, Padding, Column, Row, Stack, Positioned, Expanded, SizedBox, and Container decoration. Paste it into a fresh app to see how the pieces fit together.
import 'package:flutter/material.dart';
void main() => runApp(const MaterialApp(home: ProfileScreen()));
class ProfileScreen extends StatelessWidget {
const ProfileScreen({super.key});
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: const Text('Profile')),
body: SafeArea(
child: Padding(
padding: const EdgeInsets.all(20),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
// Avatar with an online badge using Stack + Positioned
Center(
child: Stack(
children: [
const CircleAvatar(radius: 44, child: Icon(Icons.person, size: 44)),
Positioned(
right: 2,
bottom: 2,
child: Container(
width: 18,
height: 18,
decoration: BoxDecoration(
color: Colors.green,
shape: BoxShape.circle,
border: Border.all(color: Colors.white, width: 2),
),
),
),
],
),
),
const SizedBox(height: 16),
const Center(
child: Text('Sagnik Bhattacharya',
style: TextStyle(fontSize: 22, fontWeight: FontWeight.bold)),
),
const SizedBox(height: 4),
const Center(child: Text('Flutter developer & instructor')),
const SizedBox(height: 24),
// A stats row: three Expanded cells share the width evenly
Row(
children: const [
Expanded(child: _Stat(label: 'Posts', value: '128')),
Expanded(child: _Stat(label: 'Courses', value: '6')),
Expanded(child: _Stat(label: 'Students', value: '30k')),
],
),
const SizedBox(height: 24),
// A full-width primary action
SizedBox(
width: double.infinity,
child: FilledButton(onPressed: () {}, child: const Text('Follow')),
),
],
),
),
),
);
}
}
class _Stat extends StatelessWidget {
final String label;
final String value;
const _Stat({required this.label, required this.value});
@override
Widget build(BuildContext context) {
return Column(
children: [
Text(value, style: const TextStyle(fontSize: 18, fontWeight: FontWeight.bold)),
const SizedBox(height: 2),
Text(label, style: const TextStyle(color: Colors.black54)),
],
);
}
}
Notice how no single widget does everything. The avatar badge is a Stack, the stats are three Expanded cells in a Row, the spacing is SizedBox, and the full-width button is a SizedBox with width: double.infinity. That composition style is the whole point of Flutter layout.
Common layout mistakes (and the fix)
- "RenderFlex overflowed" in a Row/Column. A child wanted more space than was available. Wrap the flexible child in
ExpandedorFlexible, or let text truncate withoverflow: TextOverflow.ellipsis. - "Unbounded height/width". You put a
ColumnorListViewinside another scrollable without bounding it. Give it a height withSizedBox, useExpandedinside a parentColumn, or setshrinkWrap: trueon the inner list. - Setting both
coloranddecorationon a Container. Move the colour into theBoxDecoration. Expandedused outside a Row/Column.ExpandedandFlexibleonly work as direct children of aFlex(Row, Column). Elsewhere you get an assertion error.- Empty
Container()collapsing or expanding unexpectedly. Be explicit: useSizedBoxfor a known size, or give the container constraints. - Overusing
IntrinsicHeight. It forces an extra measurement pass. Reach forCrossAxisAlignment.stretchor a fixed height first.
Which layout widget should I use?
- Style a box (colour, border, radius, shadow) →
ContainerwithBoxDecoration. - Just add space or a fixed size →
SizedBox. - Inset a child →
Padding. - Place a child in a position →
CenterorAlign. - Lay widgets in a line →
Row(horizontal) orColumn(vertical). - Share leftover space →
Expanded(fill) orFlexible(shrink-to-fit), withSpacerto push things apart. - Overlap or pin children →
Stack+Positioned. - Let items wrap to new lines →
Wrap. - Cap or set a size range →
ConstrainedBox,AspectRatio, orFractionallySizedBox. - React to available space →
LayoutBuilderorMediaQuery. - Scroll a long list →
ListView/GridView(see the scrolling guide).
Frequently asked questions
What are layout widgets in Flutter?
Layout widgets position and size other widgets instead of painting content. They are split into single-child widgets (Container, Padding, Center, Align, SizedBox, ConstrainedBox, AspectRatio, FittedBox) and multi-child widgets (Row, Column, Stack, Wrap, ListView, GridView).
What is the difference between Container and SizedBox?
SizedBox only sets a size or gap and can be const, so it is lighter. Container bundles padding, margin, alignment, decoration, and constraints. Use SizedBox for spacing and fixed sizes, Container when you also need decoration or padding.
How does Flutter decide a widget's size?
By the rule "constraints go down, sizes go up, the parent sets the position." A parent passes min/max width and height to each child; the child picks a size within those bounds and reports back; the parent positions it. Most layout bugs are a widget getting unbounded constraints or being forced to an impossible size.
What is the difference between Expanded and Flexible?
Both share leftover space in a Row or Column. Expanded forces the child to fill its share (tight fit). Flexible lets the child be smaller than its share (loose fit). Use Expanded to fill, Flexible to shrink-to-fit within a limit.
Further reads
Keep going with the tutorials that build directly on these layout foundations:
- Flutter Development Guide 2026 — the full Flutter hub: architecture, state, performance, testing, and more.
- Flutter Material Widgets: The Complete Catalogue With Examples — the Material components you arrange with these layout widgets.
- Flutter Cupertino Widgets: Build iOS-Style UIs — the iOS-style component set.
- Flutter Scrolling and Slivers: The Complete Guide — make these layouts scroll, from ListView to CustomScrollView.
- Flutter Animations: The Complete Guide — bring these widgets to life with motion.
- Dart Language for Flutter — the language that powers every widget here.
- Flutter Container Widget Explained — a deep dive into the everyday box: padding, decoration, and constraints.
- Flutter Row and Column — main axis, cross axis, Expanded, and spacing in depth.
- Flutter Stack and Positioned — layer and overlap widgets precisely.
- How to Fix RenderFlex Overflowed in Flutter Row inside ListView — the layout-constraints error explained end to end.
- Responsive Flutter UI for Mobile, Tablet, Desktop, and Web — breakpoints and adaptive layouts.
- Flutter App Architecture in 2026 — structuring the app these widgets live in.
- Flutter Performance in 2026 — keeping layouts fast as they grow.
Sources: Flutter documentation — Layout widgets, Understanding constraints, and the Widget catalog (docs.flutter.dev). Verified against current stable Flutter and Material 3.