Almost every confusing layout bug is explained by the constraint model.
Flutter layout follows one rule: constraints go down, sizes go up, parent sets position.
1. A parent passes each child constraints: a min and max width and height. 2. The child picks its own size within those constraints and reports it up. 3. The parent decides where to place the child.
Consequences:
- A widget can't choose any size it wants — only one inside its constraints.
Container(width: 100)directly inside the screen fills the screen, because the screen forces tight constraints. Wrap it inCenterorAlignto loosen them. - A widget doesn't know its position, and can't size itself relative to the screen without being told.
In a Row or Column, non-flexible children get unbounded space along the main axis: they're asked to be as big as they like. Then:
- `Expanded(child: ...)` forces the child to fill the leftover space, divided by
flexfactors. - `Flexible(child: ...)` lets the child be at most its share (
FlexFit.loose). - Text that would overflow a Row should be in
ExpandedorFlexiblewithoverflow: TextOverflow.ellipsis.
Errors decoded:
- "RenderFlex overflowed by N pixels" — children are larger than the Row/Column; make one flexible or scrollable.
- "Viewport was given unbounded height" — a scrollable inside a Column; give it bounded height with
ExpandedorSizedBox. - "BoxConstraints forces an infinite width" — a widget that wants to be infinitely wide inside an unbounded parent.
LayoutBuilder exposes the incoming constraints so you can choose a layout from the available space.
Example
import 'package:flutter/material.dart';
class ProductRow extends StatelessWidget {
const ProductRow({super.key, required this.title, required this.price});
final String title;
final String price;
@override
Widget build(BuildContext context) => Row(children: [
const Icon(Icons.shopping_bag),
const SizedBox(width: 8),
Expanded( // takes the remaining width
child: Text(title, maxLines: 1, overflow: TextOverflow.ellipsis),
),
Text(price), // keeps its natural size
]);
}
class SplitPanes extends StatelessWidget {
const SplitPanes({super.key});
@override
Widget build(BuildContext context) => Row(children: [
Expanded(flex: 2, child: Container(color: Colors.teal, height: 80)), // 2/3
Expanded(child: Container(color: Colors.amber, height: 80)), // 1/3
]);
}Remember
- Constraints go down, sizes go up, the parent positions
- Expanded fills leftover space by
flex; Flexible may be smaller - Center/Align loosen tight constraints so explicit sizes apply
Watch out for
- Fixing overflow by hard-coding widths, which breaks on other screen sizes.