Layout widgets arrange children on screen. Flutter does not use CSS. Parents pass constraints down, children return a size, then the parent positions them.
Places children horizontally.
Row(mainAxisAlignment: MainAxisAlignment.spaceBetween,crossAxisAlignment: CrossAxisAlignment.center,children: const [Icon(Icons.home),Text("Home"),Icon(Icons.search),],)
mainAxisAlignment → along the row (left to right)crossAxisAlignment → across the row (top to bottom)mainAxisSize → max fills width, min shrinks to childrenPlaces children vertically. Same alignment API as Row, with axes swapped.
Column(mainAxisAlignment: MainAxisAlignment.center,crossAxisAlignment: CrossAxisAlignment.stretch,children: const [Text("Title"),Text("Subtitle"),ElevatedButton(onPressed: null,child: Text("Go"),),],)
A Column inside a vertical scroll view or unbounded height can overflow. Wrap it in Expanded, Flexible, or ListView when the parent does not give a max height.
Takes the remaining space along the parent’s main axis. flex is how that space is split.
Row(children: const [Icon(Icons.star),Expanded(child: Text("This text takes the rest of the row"),),Icon(Icons.more_vert),],)
Row(children: const [Expanded(flex: 1, child: ColoredBox(color: Colors.red)),Expanded(flex: 2, child: ColoredBox(color: Colors.blue)),],)
Expanded is Flexible with fit: FlexFit.tight. The child must fill the space it is given.
Also shares leftover space, but the child can be smaller than the space (FlexFit.loose).
Row(children: const [Flexible(child: Text("Short"),),Flexible(child: Text("This long label can wrap instead of overflowing"),),],)
Use Expanded when the child should fill. Use Flexible when the child may stay smaller.
Places children on top of each other. Size comes from non-positioned children (or fit).
Stack(children: [Image.network("https://picsum.photos/400/200"),const Positioned(left: 16,bottom: 16,child: Text("Caption"),),const Align(alignment: Alignment.topRight,child: Icon(Icons.favorite),),],)
Positioned / Align sit at the top-left by default (alignment)Positioned pins a child to edgesA scrollable list. Prefer this over Column when the list can grow.
ListView(children: const [ListTile(title: Text("One")),ListTile(title: Text("Two")),ListTile(title: Text("Three")),],)
Build items lazily when the list is long:
ListView.builder(itemCount: 20,itemBuilder: (context, index) {return ListTile(title: Text("Item $index"));},)
ListView scrolls and gives children a bounded constraint on the scroll axisListView.separated adds a divider between itemsColumn? Wrap the ListView in ExpandedA scrollable grid.
GridView.count(crossAxisCount: 2,crossAxisSpacing: 8,mainAxisSpacing: 8,children: const [ColoredBox(color: Colors.red),ColoredBox(color: Colors.green),ColoredBox(color: Colors.blue),ColoredBox(color: Colors.orange),],)
Lazy version:
GridView.builder(gridDelegate: const SliverGridDelegateWithFixedCrossAxisCount(crossAxisCount: 2,childAspectRatio: 1,),itemCount: 20,itemBuilder: (context, index) {return ColoredBox(color: Colors.primaries[index % Colors.primaries.length]);},)
Flutter layout is constraints go down, sizes go up, parent sets position.
A constraint is a min/max width and min/max height:
BoxConstraints(minWidth: 0,maxWidth: 300,minHeight: 0,maxHeight: 200,)
What that means in practice:
Row / Column pass unbounded space on the main axis to children. A child that tries to be infinite (ListView, another Column with Expanded) overflowsExpanded / Flexible turn leftover space into a tight (or loose) constraintCenter / Align loosen constraints so the child can be smallerSizedBox(width: 100, height: 100) gives a tight sizeContainer with no size tries to be as big as the parent, unless it has a childDebug overflow (RIGHT OVERFLOWED BY 42 PIXELS) by asking: did the parent give a max width? If not, wrap with Expanded, Flexible, or give a width.