Layout Widgets
Now that you understand the widget tree, it's time to actually arrange widgets on screen. Flutter has no separate CSS-like layout language — layout is done entirely with widgets, the same way everything else is. This lesson covers the small set of layout widgets that cover the vast majority of real UIs: arranging things in a row or column, spacing them out, making them grow to fill space, and even overlapping them.
1. Row and Column: The Foundation
Row arranges its children horizontally; Column arranges its children vertically. They're the single most-used layout widgets in Flutter, and nearly every screen you build will contain several of them, nested inside each other.
Example: A Simple Row and Column
Column(
children: [
Text('Title'),
Row(
children: [
Icon(Icons.star),
Text('4.8 rating'),
],
),
],
)
Both widgets accept a children list, and both let you control how that content is
positioned along two separate axes: the main axis (the direction the widget lays
children out — horizontal for Row, vertical for
Column) and the cross axis (perpendicular to that).
2. mainAxisAlignment and crossAxisAlignment
These two properties are how you control spacing and alignment inside a Row or Column. The values are the same enum for both, they just apply to different axes depending on which widget you're using:
| Value | What it does |
|---|---|
start | Packs children toward the beginning of the axis (left for Row, top for Column) — this is the default |
center | Packs children together in the middle of the axis |
end | Packs children toward the end of the axis (right for Row, bottom for Column) |
spaceBetween | Spreads children out evenly, with no space before the first or after the last |
spaceAround | Spreads children out evenly, with half-size space before the first and after the last |
spaceEvenly | Spreads children out with completely equal space everywhere, including the ends |
Example: Aligning Content in a Row
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
crossAxisAlignment: CrossAxisAlignment.center,
children: [
Text('Price: \$29.99'),
ElevatedButton(onPressed: () {}, child: Text('Buy')),
],
)
3. Container: The All-Purpose Box
Container is Flutter's do-everything box widget — it can add padding, margin, a background color or decoration, a fixed width/height, and alignment for its child, all through a single set of constructor parameters. It's extremely common in real code, especially early on, because it lets you reach for one widget instead of several.
Example: Several Container Properties at Once
Container(
width: 280,
padding: EdgeInsets.all(16),
margin: EdgeInsets.symmetric(vertical: 8, horizontal: 12),
decoration: BoxDecoration(
color: Colors.white,
borderRadius: BorderRadius.circular(12),
boxShadow: [
BoxShadow(color: Colors.black12, blurRadius: 6),
],
),
child: Text('I am inside a styled Container'),
)
4. Padding and SizedBox: Simpler, More Specific Tools
Container can add padding or a fixed size, but if that's genuinely all you need, Flutter's own guidance is to reach for the more specific widget instead — Padding for spacing around a child, and SizedBox for a fixed-size box (often used as blank spacing between other widgets). The reasoning is simple: these narrower widgets communicate intent more clearly to anyone reading your code — a SizedBox obviously exists just to add a gap, while a bare Container could be doing five different things at once and a reader has to check every parameter to know which.
Example: Padding and SizedBox
Column(
children: [
Padding(
padding: EdgeInsets.all(16),
child: Text('Padded text'),
),
SizedBox(height: 20), // just a vertical gap, nothing else
Text('Some text below the gap'),
],
)
5. Expanded and Flexible: Filling Available Space
By default, a Row or Column sizes each child to that child's own natural size — it doesn't stretch anything to fill leftover space. Expanded changes that: it makes its child grow to fill whatever space is left over along the main axis. Flexible is similar but more permissive — it lets a child be as small as it wants, up to the available space, rather than forcing it to fill all of it.
Example: A Row With One Expanded Child
Row(
children: [
Expanded(
child: Text(
'This label grows to fill all remaining horizontal space',
overflow: TextOverflow.ellipsis,
),
),
Container(width: 80, child: ElevatedButton(onPressed: () {}, child: Text('Go'))),
],
)
Without the Expanded wrapper here, both children would try to take only the width they naturally need — but if the combined natural width of everything in the Row exceeds the screen's width, Flutter throws the well-known "RenderFlex overflowed" error (visible as a yellow-and-black striped warning bar on screen). Wrapping the flexible content in Expanded tells Flutter exactly how to resolve that leftover space instead of leaving it ambiguous.
6. Stack and Positioned: Overlapping Widgets
Row and Column lay children out side-by-side, never overlapping. When you actually want widgets to overlap — a badge on a corner of an icon, text on top of an image — you need Stack. Children of a Stack are painted on top of each other in order, and any child can be wrapped in Positioned to pin it to a specific spot within the stack's bounds.
Example: A Notification Badge on an Icon
Stack(
children: [
Icon(Icons.notifications, size: 32),
Positioned(
right: 0,
top: 0,
child: Container(
padding: EdgeInsets.all(4),
decoration: BoxDecoration(color: Colors.red, shape: BoxShape.circle),
child: Text(
'3',
style: TextStyle(color: Colors.white, fontSize: 10),
),
),
),
],
)
Here, the bare Icon is painted first (bottom of the stack), and the
Positioned badge is painted on top of it, pinned to the top-right
corner using right: 0 and top: 0. Any child of a
Stack that isn't wrapped in Positioned
is placed according to the stack's alignment property instead (top-left by default).
7. SingleChildScrollView: When Content Overflows
Sometimes a Column simply has more content than fits on screen — a long form, for instance. Rather than that content getting clipped or throwing an overflow error, wrap it in SingleChildScrollView to make it scrollable:
Example: A Scrollable Column
SingleChildScrollView(
child: Column(
children: [
// ...many widgets that together are taller than the screen...
],
),
)
This is a simple, single-child scrolling solution — for long or dynamically-sized lists, you'll
want the more efficient ListView instead, which only builds the
items currently visible on screen. That's covered in full in flutter-lists-listview.php.
Text, Image, Icon, buttons, and Card — that you'll place inside these layouts.