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:

ValueWhat it does
startPacks children toward the beginning of the axis (left for Row, top for Column) — this is the default
centerPacks children together in the middle of the axis
endPacks children toward the end of the axis (right for Row, bottom for Column)
spaceBetweenSpreads children out evenly, with no space before the first or after the last
spaceAroundSpreads children out evenly, with half-size space before the first and after the last
spaceEvenlySpreads 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.

Note: Expanded and Flexible only work as direct children of a Row, Column, or Flex — using them anywhere else is a compile-time/runtime error, since they rely on their parent providing flex layout information.

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.

Next up: With layout covered, move on to Common Widgets to learn the everyday building blocks — Text, Image, Icon, buttons, and Card — that you'll place inside these layouts.