Skip to content
Boring Doc
Boring Doc explanation Verified against Flutter 3.47.7 4 min readlayoutconstraintsBoxConstraintsRowColumn

Layout and constraints

Constraints go down, sizes go up, parent sets position.

Flutter lays out the tree in one pass: every parent hands its children a BoxConstraints (min/max width and height), each child picks a size inside those limits and reports it back, and the parent then decides where to place the child. Memorise it as 'constraints go down, sizes go up, parent sets position'.

  1. A constraint is four doubles: minWidth, maxWidth, minHeight, maxHeight. Tight means min == max (one possible size); loose means min is 0 and the child can be anything up to max.
  2. A widget cannot pick any size it likes and never knows its own screen position; both are decided by the chain of parents above it.
  3. Row and Column (both are Flex) lay out non-flexible children first with unbounded space on the main axis, then split what is left between Expanded/Flexible children by their flex factors.
  4. Unbounded (infinite) constraints come from scrollables and from the main axis of a Row/Column. A child that tries to be 'as big as possible' there has no answer and throws in debug mode.
  5. SizedBox and ConstrainedBox only add constraints on top of what they received; they cannot override a tight parent. Center and Align loosen constraints so a child can be smaller.
  6. The two most common layout errors, 'RenderFlex overflowed' and 'Vertical viewport was given unbounded height', are both fixed by giving the child a bounded size, usually with Expanded.
Flow diagramOne layout pass: constraints down, sizes up, parent sets position

Each render box is visited once. It receives constraints, forwards (possibly adjusted) constraints to each child in turn, collects their sizes, positions them, and finally reports its own size upward.

Packing a crate inside a crate

Think of a shipping company that nests crates. The warehouse hands you a crate and says 'anything between 0 and 300 wide, 0 and 85 tall fits in here'. You reserve a little room for padding, then tell your first package 'you may be up to 290 by 75'. It replies 'I will be 290 by 20'. You tell the second package what is left, it answers with its size, you decide where each package sits inside your crate, and finally you tell the warehouse how big your crate ended up. Nobody picks a size outside the limits they were given, and no package chooses its own shelf position.

Where the analogy breaks: Real crates always have finite walls. In Flutter a parent can hand down an infinite maximum (a scrollable in its scroll direction, a Row on its main axis), and a child that 'wants to fill the crate' then has no sensible answer. The analogy also hides that Flutter has only one pass: a parent cannot lay children out twice to negotiate, which is why Expanded exists to pre-decide how leftover space is shared.

Minimal working example

A Row that does not overflow and a Column that can hold a ListView
dart
import 'package:flutter/material.dart';

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

  @override
  Widget build(BuildContext context) {
    return Column(
      children: [
        Row(
          children: [
            const SizedBox(width: 48, height: 48, child: FlutterLogo()),
            const SizedBox(width: 12),
            const Expanded(
              child: Text(
                'A display name long enough to overflow a narrow phone',
                overflow: TextOverflow.ellipsis,
              ),
            ),
          ],
        ),
        Expanded(
          child: ListView(
            children: const [
              ListTile(title: Text('Posts')),
              ListTile(title: Text('Followers')),
            ],
          ),
        ),
      ],
    );
  }
}
Expected: The header row fits any width: the name truncates with an ellipsis. The list fills whatever height remains below the row and scrolls inside that space.Flutter ≥ 3.0.0Common Flutter errors
  1. L8The Column gets its constraints from its parent (a Scaffold body here, which gives a bounded height). It passes unbounded height to its non-flexible children.
  2. L12SizedBox hands the logo a tight 48x48 constraint. The Row gave it unbounded width, so the SizedBox's own limits win.
  3. L14Expanded tells the Row: lay out the other children first, then give this one all the remaining width as a tight constraint. The Text's natural width is ignored, so it ellipsises instead of overflowing.
  4. L22Without this Expanded the Column would offer the ListView unbounded height, and a viewport cannot size itself in an infinite space ('Vertical viewport was given unbounded height').

Use cases

  • Use Row/Column (Flex) for linear arrangements and Expanded/Flexible to say how leftover space on the main axis should be shared.
  • Use SizedBox for an exact size or a gap, ConstrainedBox to cap or floor a child, and Center/Align when a child should be allowed to be smaller than its parent.
  • Use LayoutBuilder when a widget must adapt to the space it is actually given (sidebars, cards, responsive grids) rather than to the whole screen.
  • Use Expanded around a ListView, GridView or other scrollable that sits inside a Column so the viewport receives a bounded height.

Benefits

  • Single-pass layout is fast and predictable: every render object is laid out once per frame, with cost linear in the number of nodes.
  • Because sizes are negotiated from constraints, the same widget composes correctly inside a dialog, a split pane or a full screen without code changes.

Limitations

  • A child cannot force a size the parent did not allow and cannot learn its own position; 'width: 100' on a Container is silently ignored under tight constraints.
  • Widgets that need intrinsic dimensions (IntrinsicHeight, IntrinsicWidth, some table layouts) break the single-pass model and are noticeably more expensive.
  • Unbounded constraints (scrollables, nested Rows/Columns) produce runtime assertions rather than compile-time errors, so you discover them when the widget first renders.

Alternatives and trade-offs

  • Stack and Positioned — Children must overlap or be pinned to edges instead of flowing in a line.
  • Wrap — Children should flow onto new lines when the main axis runs out of room, like chips or tags.
  • Slivers (CustomScrollView) — Several scrollable regions or collapsing headers must share one scroll position; see the performance topic for large lists.
  • Custom RenderBox or CustomMultiChildLayout — No built-in widget expresses the sizing rule you need and you are willing to implement performLayout yourself.

A ListView directly inside a Column

Problem: Column gives its children unbounded height. A ListView tries to fill the available height, infinity has no size, and layout throws 'Vertical viewport was given unbounded height' followed by 'RenderBox was not laid out'.

Fix: Give the viewport a bounded height: wrap it in Expanded (take the remaining space), Flexible, or a SizedBox with an explicit height. Use shrinkWrap only for short lists because it forces the list to measure every child.

✗ Unbounded height
dart
Column(
  children: [
    const Text('Header'),
    ListView(children: const [ListTile(title: Text('Map'))]), // throws
  ],
)
✓ Bounded by Expanded
dart
Column(
  children: [
    const Text('Header'),
    Expanded(
      child: ListView(children: const [ListTile(title: Text('Map'))]),
    ),
  ],
)

Text or a Column inside a Row overflows to the right

Problem: A Row does not constrain the width of its non-flexible children, so a long Text (or a Column containing one) asks for its full intrinsic width, which can exceed the Row. You see yellow and black stripes and 'A RenderFlex overflowed by N pixels on the right'.

Fix: Wrap the child in Expanded or Flexible so the Row assigns it the remaining width instead of letting it choose. Expanded is Flexible with fit: FlexFit.tight and flex: 1.

✗ Unconstrained child in a Row
dart
Row(
  children: [
    const Icon(Icons.message),
    Column(
      crossAxisAlignment: CrossAxisAlignment.start,
      children: const [Text('Title'), Text('Very long body text ...')],
    ),
  ],
)
✓ Row assigns the remaining width
dart
Row(
  children: [
    const Icon(Icons.message),
    Expanded(
      child: Column(
        crossAxisAlignment: CrossAxisAlignment.start,
        children: const [Text('Title'), Text('Very long body text ...')],
      ),
    ),
  ],
)

Expecting Container(width: 100) to be 100 wide

Problem: At the root of the app, and under many other parents, the incoming constraints are tight: the screen says 'be exactly this size'. Container honours the constraints it was given, so the width argument is ignored and it fills the screen.

Fix: Loosen the constraints first. Center, Align or a parent that does not force a size lets the Container pick its own width. If you are unsure what constraints arrive, read them with LayoutBuilder or inspect the render object in DevTools.

✗ Tight constraints override width
dart
void main() {
  runApp(Container(width: 100, height: 100, color: Colors.red)); // fills screen
}
✓ Center loosens the constraints
dart
void main() {
  runApp(
    Center(child: Container(width: 100, height: 100, color: Colors.red)),
  );
}

Expanded inside a Column that is itself unbounded

Problem: Put a Column inside a ListView or SingleChildScrollView and the Column receives infinite height. An Expanded child then asks for 'the rest of infinity', and the framework throws because a flex child cannot have a non-zero flex when the main axis is unbounded.

Fix: Either give the Column a bounded height (SizedBox, or make the parent non-scrollable) or drop the Expanded and let children size themselves with mainAxisSize: MainAxisSize.min. For long content, replace the Column with the scrollable itself.

This page is Boring Doc’s independent explanation. For the complete specification use the official pages below (content © The Flutter Authors, CC BY 3.0; code samples BSD). Dates show when the explanation was last checked and when the official page last changed.

  • Understanding constraints Flutter 3.47.7 Explanation verified against current source
    https://docs.flutter.dev/ui/layout/constraints
    Last checked
    Oct 21, 2026
    Source last updated
    3 weeks ago
    Last synchronized
    3 weeks ago
  • Layouts in Flutter Flutter 3.47.7 Explanation verified against current source
    https://docs.flutter.dev/ui/layout
    Last checked
    Oct 21, 2026
    Source last updated
    3 weeks ago
    Last synchronized
    3 weeks ago
  • Layout (learning pathway) Flutter 3.47.7 Explanation verified against current source
    https://docs.flutter.dev/learn/pathway/tutorial/layout
    Last checked
    Oct 21, 2026
    Source last updated
    3 weeks ago
    Last synchronized
    3 weeks ago
  • Common Flutter errors Flutter 3.47.7 Explanation verified against current source
    https://docs.flutter.dev/testing/common-errors
    Last checked
    Oct 21, 2026
    Source last updated
    3 weeks ago
    Last synchronized
    3 weeks ago
  • https://api.flutter.dev/flutter/rendering/BoxConstraints-class.html
    Last checked
    Oct 21, 2026
Was this useful?
AI explanationAI-generated · grounded in cited sources
AI explanations are not configured
Set AI_PROVIDER (for example to openai-compatible with a local Ollama or LM Studio endpoint) to enable grounded explanations. Everything else on this page works without an AI provider.