If you've built anything beyond a single-screen prototype in Flutter, you've already reached for Row and Column. These two widgets, paired with Expanded and Flexible, form the backbone of nearly every layout you'll create. Understanding how they interact—and when to use each one—separates layouts that feel natural from those that break under real-world content.
Row and Column: The Direction Containers
Row and Column are the simplest yet most powerful layout primitives in Flutter. Row arranges children horizontally along the main axis, while Column arranges them vertically. That's it. But simplicity here is deceptive—these widgets control how space is distributed, how children align, and how your UI responds to different screen sizes.
Let me show you a practical example. Imagine a contact card in a CRM application where you display a person's avatar, name, and role:
Row(
children: [
const CircleAvatar(
radius: 24,
backgroundImage: NetworkImage('https://example.com/avatar.jpg'),
),
const SizedBox(width: 12),
Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
'Sarah Chen',
style: Theme.of(context).textTheme.titleMedium,
),
Text(
'Product Manager',
style: Theme.of(context).textTheme.bodySmall,
),
],
),
],
);Here, Row positions the avatar and the Column (which holds name and role) side by side. The Row's main axis is horizontal; the Column nested inside uses its own vertical main axis. This nesting pattern is fundamental to building complex layouts—every screen is ultimately a tree of Rows and Columns.
One critical point: both Row and Column expect their children to fit within the available space. If a child's content is too large, you'll see the dreaded "overflow pixels" warning. This is where Expanded and Flexible enter the picture.
Expanded: Taking Up Available Space
Expanded tells a child widget to grow and fill all remaining space along the main axis. In a Row, it grows horizontally. In a Column, it grows vertically. Without Expanded, your text, buttons, and other widgets size themselves based on their content alone.
Back to that contact card—what if the name is very long? Or what if you want the card to stretch across the full width of the container? Wrap the Column in Expanded:
Row(
children: [
const CircleAvatar(
radius: 24,
backgroundImage: NetworkImage('https://example.com/avatar.jpg'),
),
const SizedBox(width: 12),
Expanded(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
'Sarah Chen',
style: Theme.of(context).textTheme.titleMedium,
),
Text(
'Product Manager',
style: Theme.of(context).textTheme.bodySmall,
),
],
),
),
],
);Now the Column expands to fill the space between the avatar and the right edge of the Row. The text can grow, and the layout remains stable. If you have multiple Expanded widgets in the same Row or Column, they share the available space equally—or you can customize this with the flex parameter.
I used this pattern constantly when building the ride-hailing platform at my previous role. Trip cards needed to show driver info, vehicle type, and rating in a compact row. Expanded ensured the driver's name and vehicle details stretched nicely across different phone widths without hardcoding pixel values.
Flexible: Allowing Children to Shrink
Flexible is Expanded's more permissive cousin. While Expanded forces a child to grow and fill space, Flexible lets a child grow up to available space but also permits it to be smaller. The fit parameter controls this behavior: fit: FlexFit.tight mirrors Expanded; fit: FlexFit.loose (the default) allows the child to size itself.
Here's a real scenario: you're building a chat message list. Some messages are short ("OK"), others are long paragraphs. You want the message text to expand when needed but not force a single-word message to stretch across the screen:
Row(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
const CircleAvatar(radius: 20),
const SizedBox(width: 8),
Flexible(
child: Container(
decoration: BoxDecoration(
color: Colors.grey[300],
borderRadius: BorderRadius.circular(12),
),
padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 8),
child: Text(message.text),
),
),
const SizedBox(width: 8),
const Icon(Icons.more_vert, size: 20),
],
);Flexible here lets the message bubble grow to fill space when text is long, but it won't force "OK" to stretch. Without Flexible, if the text exceeded the Row's width, you'd see overflow. With Flexible, the child can wrap and shrink intelligently.
In production trading platforms, I used Flexible in order books where prices, volumes, and order statuses needed different amounts of space. Flexible prevented brittle layouts where one column's content could break the alignment of others.
mainAxisAlignment and crossAxisAlignment
After placing and sizing children, you need to position them within the container. mainAxisAlignment controls alignment along the main axis (horizontally in Row, vertically in Column). crossAxisAlignment controls alignment perpendicular to the main axis.
These properties take values like:
- start: aligns children at the beginning
- end: aligns children at the end
- center: centers children
- spaceBetween: distributes children with equal space between them
- spaceAround: distributes children with equal space around them
- spaceEvenly: equal space everywhere
Let me show a practical example from an e-commerce product listing page:
Column(
children: [
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
crossAxisAlignment: CrossAxisAlignment.center,
children: [
Text(
'Product Name',
style: Theme.of(context).textTheme.titleLarge,
),
IconButton(
icon: const Icon(Icons.favorite_border),
onPressed: () {},
),
],
),
const SizedBox(height: 8),
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
crossAxisAlignment: CrossAxisAlignment.baseline,
textBaseline: TextBaseline.alphabetic,
children: [
Text(
'\$49.99',
style: Theme.of(context).textTheme.titleMedium,
),
Text(
'\$99.99',
style: Theme.of(context).textTheme.bodySmall?.copyWith(
decoration: TextDecoration.lineThrough,
),
),
],
),
],
);The first Row uses mainAxisAlignment.spaceBetween to push the product name to the left and the heart icon to the right. The second Row uses crossAxisAlignment.baseline to align the text prices by their alphabetic baseline, which looks more professional than center alignment.
crossAxisAlignment.stretch is another powerful option—it forces children to fill the entire cross-axis. In a Column with stretch, all children expand to the full width of the container. This is cleaner than manually setting width on every child.
Combining All Four: A Real Example
Let me tie this together with a more complex example—a travel booking confirmation card from an app I worked on:
Container(
padding: const EdgeInsets.all(16),
decoration: BoxDecoration(
border: Border.all(color: Colors.grey[300]),
borderRadius: BorderRadius.circular(8),
),
child: Column(
children: [
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text('Jakarta'),
const SizedBox(height: 4),
Text('Dec 20, 10:00 AM', style: Theme.of(context).textTheme.bodySmall),
],
),
Icon(Icons.arrow_forward, color: Colors.grey[400]),
Column(
crossAxisAlignment: CrossAxisAlignment.end,
children: [
Text('Singapore'),
const SizedBox(height: 4),
Text('Dec 20, 2:30 PM', style: Theme.of(context).textTheme.bodySmall),
],
),
],
),
const SizedBox(height: 16),
Row(
children: [
Icon(Icons.person, size: 16),
const SizedBox(width: 8),
Expanded(
child: Text('1 Passenger'),
),
Icon(Icons.luggage, size: 16),
const SizedBox(width: 8),
Expanded(
child: Text('2 Bags'),
),
],
),
const SizedBox(height: 16),
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Text('Total Price'),
Text(
'\$256.00',
style: Theme.of(context).textTheme.titleLarge,
),
],
),
],
),
);This card uses Column as the root, with multiple Rows inside. The departure/arrival Row uses spaceBetween to separate origin and destination. The passenger/baggage Row uses Expanded twice to distribute space evenly. The final price Row also uses spaceBetween to push price and amount apart. All of this responds automatically to different screen sizes because we've used relative spacing and alignment rather than hardcoded pixels.
Common Mistakes
- Forgetting Expanded with long content: Text inside a Row that exceeds the screen width will overflow unless wrapped in Expanded or Flexible. This is the most common layout crash I see in code reviews.
- Nesting too many Expanded widgets without flex: If you have three Expanded children in a Row, each gets one-third of the space. But if you set flex: 2 on one, it gets half. Forgetting to tune flex can lead to unbalanced layouts.
- Using crossAxisAlignment.baseline without textBaseline: Baseline alignment requires specifying TextBaseline.alphabetic or TextBaseline.ideographic. Without it, you'll get a runtime error.
- Putting unbounded Columns inside Rows: A Column inside a Row has unlimited height. If that Column has children that also want unlimited height, you'll crash. Always constrain or use Expanded with appropriate flex.
- Over-relying on mainAxisSize: By default, Row has mainAxisSize: MainAxisSize.max, meaning it expands to fill the parent. Column has the same. If you want tight packing, set mainAxisSize: MainAxisSize.min, but this is rare.
Key Takeaways
- Row and Column are direction-based containers; Row goes horizontal, Column vertical. Nest them to build complex layouts.
- Expanded forces a child to grow and fill remaining main-axis space; use it whenever content should stretch across available room.
- Flexible allows a child to grow up to available space but shrink if needed; use it for content that adapts to its surroundings.
- mainAxisAlignment positions children along the main axis (start, center, spaceBetween, etc.); crossAxisAlignment positions them perpendicular to it.
- Always test with long text and different screen sizes. Most layout bugs appear when content doesn't fit the assumptions you made during design.
- Combine mainAxisAlignment and crossAxisAlignment strategically to create professional, responsive interfaces without hardcoding dimensions.






Comments
Loading comments…