Rows and Columns
Arranging multiple widgets horizontally and vertically, and controlling how they share space.
3 min read
Almost every Flutter screen is built from two widgets at its core: Row arranges children horizontally, Column arranges children vertically. Everything else in layout is refinement on top of these two.
The basics
Row(
children: [
Icon(Icons.star),
SizedBox(width: 8),
Text('4.8 rating'),
],
)Column(
children: [
Text('Title', style: TextStyle(fontSize: 20)),
Text('Subtitle', style: TextStyle(color: Colors.grey)),
],
)Both take a children list and lay them out one after another along their respective axis — Row along the horizontal (main axis), Column along the vertical. Understanding "main axis" versus "cross axis" is the key to controlling either widget: for a Row, the main axis is horizontal and the cross axis is vertical; for a Column, it's reversed.
Controlling alignment
mainAxisAlignment and crossAxisAlignment control spacing and alignment along each axis:
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
crossAxisAlignment: CrossAxisAlignment.center,
children: [
const Text('Left'),
const Text('Right'),
],
)MainAxisAlignment.spaceBetween pushes the first child to the start and the last to the end, distributing remaining space between them — useful for headers with a title on the left and an action on the right. Other common values are start, end, center, and spaceEvenly.
Making a child take remaining space
By default, Row and Column size each child to its natural size. To make one child stretch and fill whatever space is left over, wrap it in Expanded:
Row(
children: [
const Icon(Icons.search),
Expanded(
child: TextField(
decoration: const InputDecoration(hintText: 'Search...'),
),
),
const Icon(Icons.mic),
],
)Here, the icons take only the space they need, and Expanded tells the TextField to consume everything left over — this is the standard pattern for a search bar that should fill available width between fixed-size icons.
Flexible is a related widget worth knowing: Expanded is really Flexible with flex forcing the child to fill the space; plain Flexible lets the child be smaller than the available space if it wants to, while still capping it from overflowing.
A common overflow bug
A Row or Column errors with a yellow-and-black striped overflow warning when its children need more space than is available and nothing is set up to shrink or scroll:
// If this text is long and the Row has other fixed-width children,
// it may overflow horizontally.
Row(
children: [
const Icon(Icons.warning),
Text(veryLongProductName), // wrap in Expanded to fix
],
)Wrapping the offending child in Expanded (so it shrinks to fit) or in a SingleChildScrollView (so it scrolls instead) are the two standard fixes — recognizing this pattern will save you real debugging time.
Nesting rows and columns
Real layouts nest Rows inside Columns and vice versa freely:
Column(
children: [
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: const [Text('Order #1024'), Text('\$42.00')],
),
const SizedBox(height: 4),
const Text('3 items', style: TextStyle(color: Colors.grey)),
],
)This is exactly how card-style UI — a header row, followed by supporting text — gets built in practice. Master Row, Column, and Expanded, and you can lay out the vast majority of real app screens.