Dart Tutorial

Flutter Lesson 14 of 83 3 min read

How Layout Works in Flutter: Constraints Explained

Understand Flutter layout: constraints go down, sizes go up, parent sets position. Learn to fix overflow and unbounded size errors.

On this page

Flutter layout follows one rule. Learn it and most layout errors stop being mysterious.

Constraints go down. Sizes go up. Parent sets position.

  1. A parent tells each child its constraints: a minimum and maximum width and height.
  2. The child picks a size within those limits and reports it back.
  3. The parent decides where to place the child.

A widget does not know its own position, and it cannot be any size it likes. It can only choose within what its parent allows.

Seeing it happen #

import 'package:flutter/material.dart';

void main() {
  runApp(
    MaterialApp(
      home: Center(
        child: Container(width: 200, height: 100, color: Colors.amber),
      ),
    ),
  );
}

Remove the Center, so the container is the home directly, and it fills the whole screen, ignoring width and height. Why? The screen gives its child tight constraints: “be exactly this big”. The container has no choice. Center passes on loose constraints: “be any size up to this”, so the container can use its own size.

Tight, loose and unbounded #

ConstraintMeaningGiven by
TightExactly one sizeSizedBox(width: 100, height: 50), the screen itself
LooseAnything from zero to a maximumCenter, Align
UnboundedNo maximum in one directionColumn and ListView on their main axis, Row horizontally

Error 1: a RenderFlex overflowed #

The yellow and black stripes mean the children of a Row or Column need more room than there is.

// Overflows on a narrow screen.
Row(
  children: const [
    Icon(Icons.info),
    Text('A long message that does not fit across the screen of a small phone'),
  ],
)

// Fixed: the text takes the remaining width and wraps.
Row(
  children: const [
    Icon(Icons.info),
    Expanded(
      child: Text('A long message that does not fit across the screen of a small phone'),
    ),
  ],
)

For a Column that is too tall, make it scrollable by wrapping it in a SingleChildScrollView.

Error 2: unbounded height #

“Vertical viewport was given unbounded height” appears when a scrolling widget is placed inside a Column. The column offers unlimited height, and the list tries to take all of it.

// Error.
Column(
  children: [
    const Text('Messages'),
    ListView(children: const [Text('One'), Text('Two')]),
  ],
)

// Fixed: the list gets whatever height is left.
Column(
  children: [
    const Text('Messages'),
    Expanded(
      child: ListView(children: const [Text('One'), Text('Two')]),
    ),
  ],
)

The same happens with a TextField in a Row (unbounded width). The fix is the same: Expanded.

Limiting size yourself #

ConstrainedBox(
  constraints: const BoxConstraints(maxWidth: 600),
  child: const Text('Never wider than 600, however large the screen'),
)

const SizedBox(width: 120, height: 48, child: Placeholder())

const FractionallySizedBox(widthFactor: 0.8, child: Placeholder()) // 80% of the parent

Centring content with a maximum width is the simplest way to keep a phone layout looking sensible on a tablet or desktop.

Asking what space you have #

LayoutBuilder hands you the constraints so you can decide what to build.

LayoutBuilder(
  builder: (context, constraints) {
    return constraints.maxWidth > 600
        ? const Text('Wide layout')
        : const Text('Narrow layout');
  },
)

Debugging layout #

  • Open the Widget Inspector in DevTools and click a widget to see its size and constraints.
  • Read the error text. It names the widget that overflowed and by how many pixels.
  • Wrap a suspicious widget in a brightly coloured ColoredBox to see how big it really is.

Try it yourself #

Put a Container with a fixed size directly as the home of a MaterialApp, then wrap it in Center, then in Align(alignment: Alignment.bottomRight). Explain each result using the rule.

Practise in the playground Updated by Santosh Adhikari