Row & Column Layout
Horizontal and Vertical Arrangement
Open interactive version (quiz + challenge)Real-world analogy
Row and Column are like organizing books. A Row is a bookshelf where books stand side by side horizontally. A Column is a stack of books piled on top of each other vertically. MainAxisAlignment is how you space them along the shelf (or stack), and CrossAxisAlignment is how you align them perpendicular to that direction.
What is it?
Row and Column are the two fundamental layout widgets in Flutter. Row arranges children horizontally, Column arranges them vertically. Together with MainAxisAlignment, CrossAxisAlignment, Expanded, Flexible, and Spacer, they handle the vast majority of Flutter layouts through simple composition.
Real-world relevance
In team_mvp_kit, nearly every screen uses Row and Column. Login forms use Column to stack fields vertically. App bars use Row for horizontal icon arrangement. List tiles combine both: a Row with an icon, a Column of title and subtitle, and a trailing action. Mastering Row and Column is mastering Flutter layout.
Key points
- Column Widget — Column arranges its children widgets vertically from top to bottom. Its main axis runs vertically and its cross axis runs horizontally. Wrap multiple widgets in a Column to stack them. By default, a Column takes the full available height of its parent.
- Row Widget — Row arranges its children widgets horizontally from left to right. Its main axis runs horizontally and its cross axis runs vertically. Use Row to place widgets side by side, like an icon next to a label or a profile picture beside a username.
- MainAxisAlignment — MainAxisAlignment controls how children are spaced along the main axis (vertical for Column, horizontal for Row). Values include: start (bunch at beginning), end (bunch at end), center (middle), spaceBetween (equal space between), spaceAround (equal space around each), and spaceEvenly (equal space everywhere).
- CrossAxisAlignment — CrossAxisAlignment controls how children align along the cross axis (horizontal for Column, vertical for Row). Values include: start, end, center (default), stretch (expand to fill cross axis), and baseline (align text baselines). Use stretch to make all children the same width in a Column.
- Expanded Widget — Expanded makes a child widget fill all remaining space along the main axis. If multiple Expanded widgets exist, they share the space equally by default. Use the flex property to control the ratio: flex: 2 takes twice the space of flex: 1. Expanded prevents overflow errors when content is too wide or tall.
- Flexible Widget — Flexible is like Expanded but optional — it CAN take extra space but does not have to. With FlexFit.loose, the child sizes itself naturally but won't exceed its flex share. With FlexFit.tight (which is what Expanded uses), the child MUST fill its flex share. Use Flexible when you want proportional sizing without forcing fill.
- Spacer Widget — Spacer is a convenience widget that creates an empty Expanded widget. It takes up all remaining space between widgets in a Row or Column. Use Spacer to push widgets apart without calculating padding manually. Multiple Spacers share space equally.
- Nesting Row and Column — Real layouts combine Row and Column by nesting them. A Column can contain Rows, and Rows can contain Columns. This composition pattern builds any layout: a card with a title row and description column, a list item with icon, text column, and trailing action.
- MainAxisSize — MainAxisSize controls whether a Row or Column takes the maximum or minimum space along its main axis. MainAxisSize.max (default) stretches to fill the parent. MainAxisSize.min shrinks to fit its children. Use min when you want a Row or Column to be only as big as its content.
Code example
import 'package:flutter/material.dart';
class UserCard extends StatelessWidget {
final String name;
final String email;
final String role;
const UserCard({
super.key,
required this.name,
required this.email,
required this.role,
});
@override
Widget build(BuildContext context) {
return Card(
child: Padding(
padding: const EdgeInsets.all(16),
child: Row(
children: [
const CircleAvatar(
radius: 28,
child: Icon(Icons.person, size: 28),
),
const SizedBox(width: 16),
Expanded(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
name,
style: Theme.of(context).textTheme.titleMedium,
),
const SizedBox(height: 4),
Text(
email,
style: Theme.of(context).textTheme.bodySmall,
),
],
),
),
Container(
padding: const EdgeInsets.symmetric(
horizontal: 12,
vertical: 6,
),
decoration: BoxDecoration(
color: Colors.blue.shade50,
borderRadius: BorderRadius.circular(16),
),
child: Text(
role,
style: TextStyle(
color: Colors.blue.shade700,
fontSize: 12,
fontWeight: FontWeight.w600,
),
),
),
],
),
),
);
}
}Line-by-line walkthrough
- 1. Import the Material package
- 2.
- 3. Define UserCard as a StatelessWidget since it just displays data
- 4. Declare required fields: name, email, and role
- 5.
- 6.
- 7.
- 8.
- 9. Build method returns the widget tree
- 10. Return a Card widget for the elevated surface look
- 11. Pad the card content by 16 pixels on all sides
- 12.
- 13. Row arranges avatar, text, and badge horizontally
- 14.
- 15. CircleAvatar creates a round profile picture placeholder
- 16. Set radius to 28 for a medium-sized avatar
- 17. Person icon inside the avatar
- 18.
- 19. SizedBox adds 16px horizontal gap between avatar and text
- 20. Expanded makes the text column fill remaining space
- 21. Column stacks name and email vertically
- 22. CrossAxisAlignment.start left-aligns the text
- 23.
- 24. Display the name with titleMedium theme style
- 25.
- 26.
- 27. SizedBox adds 4px gap between name and email
- 28.
- 29. Display email with bodySmall theme style (lighter color)
- 30.
- 31.
- 32.
- 33. Container wraps the role badge
- 34. Symmetric padding: 12px horizontal, 6px vertical
- 35.
- 36. BoxDecoration adds background color and rounded corners
- 37. Light blue background
- 38. 16px border radius for pill shape
- 39.
- 40. Text displays the role string
- 41. Styled with dark blue color, small font, semibold weight
- 42.
- 43.
- 44.
- 45.
- 46.
- 47.
- 48.
Spot the bug
Row(
children: [
Text('A very long text that keeps going and going'),
Text('Another long text that also keeps going'),
],
)Need a hint?
What happens when children in a Row are wider than the screen?
Show answer
The Row overflows because both Text widgets try to take their natural width, which exceeds the screen. Fix: wrap one or both Text widgets in Expanded or Flexible so they share the available space and wrap their text: Expanded(child: Text('A very long text...'))
Explain like I'm 5
Imagine you have toy blocks. Column is when you stack blocks into a tower going up. Row is when you line blocks up in a train going sideways. MainAxisAlignment is like choosing whether to bunch the blocks at one end, spread them out evenly, or put them in the middle. And Expanded is like a stretchy block that grows to fill any empty space so there are no gaps!
Fun fact
Flutter's layout system is inspired by the CSS Flexbox model from web development. Row is like display: flex with flex-direction: row, and Column is like flex-direction: column. If you know Flexbox, you already understand Flutter layout!
Hands-on challenge
Build a profile card that uses a Row for the top section (avatar on the left, Column of name and bio on the right), a Row of stat counters (posts, followers, following) using Expanded with equal flex, and a Row of two buttons (Follow as Expanded ElevatedButton, Message as Expanded OutlinedButton) with a SizedBox gap between them.
More resources
- Layouts in Flutter (Flutter Official)
- Row Class (Flutter API)
- Understanding Constraints (Flutter Official)