Row
Understand how to use Row for horizontal layout.
What is it?
Row is a widget that displays its children in a horizontal array. It's one of the most commonly used layout widgets in Flutter. Row arranges widgets from left to right (or right to left in RTL locales) and can control how they align, distribute space, and size themselves.
Why does it exist?
Row exists to:
- Arrange widgets horizontally
- Create responsive horizontal layouts
- Control alignment and spacing
- Distribute available space
- Support scrolling when needed
- Build complex UI components
- Enable flexible and adaptive designs
Basic Row
Row arranges children horizontally.
// Basic Row usage
class BasicRow extends StatelessWidget {
const BasicRow({super.key});
@override
Widget build(BuildContext context) {
return Row(
children: [
// 1. Container with fixed size
Container(
width: 50,
height: 50,
color: Colors.red,
),
// 2. Container with fixed size
Container(
width: 50,
height: 50,
color: Colors.green,
),
// 3. Container with fixed size
Container(
width: 50,
height: 50,
color: Colors.blue,
),
],
);
}
}
// Row properties:
// 1. children - List of widgets
// 2. mainAxisAlignment - Horizontal alignment
// 3. crossAxisAlignment - Vertical alignment
// 4. mainAxisSize - Min or max space
// 5. textDirection - LTR or RTL
// 6. verticalDirection - Up or down
// 7. textBaseline - Text baseline
What's happening here? - Children are arranged left to right - Each child has fixed size - Row takes the height of tallest child - Width is sum of children + spacing - Can control alignment and sizing
MainAxisAlignment
MainAxisAlignment controls horizontal alignment.
// MainAxisAlignment examples
class RowMainAxisAlignment extends StatelessWidget {
const RowMainAxisAlignment({super.key});
@override
Widget build(BuildContext context) {
return Column(
children: [
// 1. Start - Left aligned
_buildRowWithTitle(
'Start',
mainAxisAlignment: MainAxisAlignment.start,
),
// 2. Center - Center aligned
_buildRowWithTitle(
'Center',
mainAxisAlignment: MainAxisAlignment.center,
),
// 3. End - Right aligned
_buildRowWithTitle(
'End',
mainAxisAlignment: MainAxisAlignment.end,
),
// 4. SpaceBetween - Space between
_buildRowWithTitle(
'SpaceBetween',
mainAxisAlignment: MainAxisAlignment.spaceBetween,
),
// 5. SpaceAround - Space around
_buildRowWithTitle(
'SpaceAround',
mainAxisAlignment: MainAxisAlignment.spaceAround,
),
// 6. SpaceEvenly - Even space
_buildRowWithTitle(
'SpaceEvenly',
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
),
],
);
}
Widget _buildRowWithTitle(
String title, {
required MainAxisAlignment mainAxisAlignment,
}) {
return Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
title,
style: const TextStyle(fontWeight: FontWeight.bold),
),
Container(
width: double.infinity,
color: Colors.grey[200],
child: Row(
mainAxisAlignment: mainAxisAlignment,
children: [
Container(width: 40, height: 40, color: Colors.red),
Container(width: 40, height: 40, color: Colors.green),
Container(width: 40, height: 40, color: Colors.blue),
],
),
),
const SizedBox(height: 10),
],
);
}
}
What's happening here? - start aligns children to the left - center centers children - end aligns children to the right - spaceBetween distributes space between - spaceAround distributes space around - spaceEvenly distributes space evenly
CrossAxisAlignment
CrossAxisAlignment controls vertical alignment.
// CrossAxisAlignment examples
class RowCrossAxisAlignment extends StatelessWidget {
const RowCrossAxisAlignment({super.key});
@override
Widget build(BuildContext context) {
return Column(
children: [
// 1. Start - Align to top
_buildRowWithTitle(
'Start',
crossAxisAlignment: CrossAxisAlignment.start,
),
// 2. Center - Align to center
_buildRowWithTitle(
'Center',
crossAxisAlignment: CrossAxisAlignment.center,
),
// 3. End - Align to bottom
_buildRowWithTitle(
'End',
crossAxisAlignment: CrossAxisAlignment.end,
),
// 4. Stretch - Stretch to fill
_buildRowWithTitle(
'Stretch',
crossAxisAlignment: CrossAxisAlignment.stretch,
),
// 5. Baseline - Align by text baseline
_buildRowWithTitle(
'Baseline',
crossAxisAlignment: CrossAxisAlignment.baseline,
textBaseline: TextBaseline.alphabetic,
),
],
);
}
Widget _buildRowWithTitle(
String title, {
required CrossAxisAlignment crossAxisAlignment,
TextBaseline? textBaseline,
}) {
return Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
title,
style: const TextStyle(fontWeight: FontWeight.bold),
),
Container(
height: 100,
width: double.infinity,
color: Colors.grey[200],
child: Row(
crossAxisAlignment: crossAxisAlignment,
textBaseline: textBaseline,
children: [
Container(width: 40, height: 30, color: Colors.red),
Container(width: 40, height: 60, color: Colors.green),
Container(width: 40, height: 45, color: Colors.blue),
],
),
),
const SizedBox(height: 10),
],
);
}
}
What's happening here? - start aligns children to the top - center centers children vertically - end aligns children to the bottom - stretch stretches children to fill height - baseline aligns by text baseline
MainAxisSize
MainAxisSize controls how much space Row takes.
// MainAxisSize examples
class RowMainAxisSize extends StatelessWidget {
const RowMainAxisSize({super.key});
@override
Widget build(BuildContext context) {
return Column(
children: [
// 1. Min - Takes minimum space
Container(
color: Colors.grey[200],
child: Row(
mainAxisSize: MainAxisSize.min,
children: [
Container(width: 50, height: 50, color: Colors.red),
Container(width: 50, height: 50, color: Colors.green),
Container(width: 50, height: 50, color: Colors.blue),
],
),
),
const SizedBox(height: 10),
// 2. Max - Takes maximum space
Container(
color: Colors.grey[200],
child: Row(
mainAxisSize: MainAxisSize.max,
children: [
Container(width: 50, height: 50, color: Colors.red),
Container(width: 50, height: 50, color: Colors.green),
Container(width: 50, height: 50, color: Colors.blue),
],
),
),
],
);
}
}
// MainAxisSize effects:
// Min: Row is as wide as children + spacing
// Max: Row expands to fill parent width
// Default is MainAxisSize.max
// Use min for tight wrapping, max for full width
What's happening here? - min takes minimum space needed - max expands to fill available space - Default is max - min is useful for tight layouts
Row with Expanded
Expanded makes children fill available space.
// Row with Expanded children
class RowWithExpanded extends StatelessWidget {
const RowWithExpanded({super.key});
@override
Widget build(BuildContext context) {
return Column(
children: [
// 1. Equal distribution
Container(
color: Colors.grey[200],
child: Row(
children: [
Expanded(
child: Container(
height: 50,
color: Colors.red,
child: const Center(child: Text('1')),
),
),
Expanded(
child: Container(
height: 50,
color: Colors.green,
child: const Center(child: Text('1')),
),
),
Expanded(
child: Container(
height: 50,
color: Colors.blue,
child: const Center(child: Text('1')),
),
),
],
),
),
const SizedBox(height: 10),
// 2. Proportional distribution
Container(
color: Colors.grey[200],
child: Row(
children: [
Expanded(
flex: 1,
child: Container(
height: 50,
color: Colors.red,
child: const Center(child: Text('1')),
),
),
Expanded(
flex: 2,
child: Container(
height: 50,
color: Colors.green,
child: const Center(child: Text('2')),
),
),
Expanded(
flex: 3,
child: Container(
height: 50,
color: Colors.blue,
child: const Center(child: Text('3')),
),
),
],
),
),
const SizedBox(height: 10),
// 3. Mixed fixed and expanded
Container(
color: Colors.grey[200],
child: Row(
children: [
Container(
width: 80,
height: 50,
color: Colors.red,
child: const Center(child: Text('Fixed')),
),
Expanded(
child: Container(
height: 50,
color: Colors.green,
child: const Center(child: Text('Expanded')),
),
),
Container(
width: 80,
height: 50,
color: Colors.blue,
child: const Center(child: Text('Fixed')),
),
],
),
),
],
);
}
}
What's happening here? - Expanded children fill remaining space - Flex factors control proportion - Fixed children keep their size - Mixed layouts combine fixed and flexible - Very common for responsive layouts
Row with Flexible
Flexible gives more control than Expanded.
// Row with Flexible children
class RowWithFlexible extends StatelessWidget {
const RowWithFlexible({super.key});
@override
Widget build(BuildContext context) {
return Column(
children: [
// 1. Flexible with tight fit
Container(
color: Colors.grey[200],
child: Row(
children: [
Flexible(
fit: FlexFit.tight,
child: Container(
height: 50,
color: Colors.red,
child: const Center(child: Text('Tight')),
),
),
Flexible(
fit: FlexFit.tight,
child: Container(
height: 50,
color: Colors.green,
child: const Center(child: Text('Tight')),
),
),
],
),
),
const SizedBox(height: 10),
// 2. Flexible with loose fit
Container(
color: Colors.grey[200],
child: Row(
children: [
Flexible(
fit: FlexFit.loose,
child: Container(
height: 50,
color: Colors.red,
child: const Center(child: Text('Loose')),
),
),
Flexible(
fit: FlexFit.loose,
child: Container(
height: 50,
color: Colors.green,
child: const Center(child: Text('Loose')),
),
),
],
),
),
const SizedBox(height: 10),
// 3. Flexible with different flex values
Container(
color: Colors.grey[200],
child: Row(
children: [
Flexible(
flex: 2,
fit: FlexFit.tight,
child: Container(
height: 50,
color: Colors.red,
child: const Center(child: Text('Flex 2')),
),
),
Flexible(
flex: 1,
fit: FlexFit.tight,
child: Container(
height: 50,
color: Colors.blue,
child: const Center(child: Text('Flex 1')),
),
),
],
),
),
],
);
}
}
// Expanded vs Flexible:
// Expanded = Flexible(fit: FlexFit.tight)
// Flexible allows more control over fitting behavior
// Loose fit allows child to size naturally
// Tight fit forces child to fill space
What's happening here? - Flexible controls flex behavior - Tight fit forces fill - Loose fit allows natural size - Flex factors work the same
Row with Spacing
Adding spacing between children.
// Row spacing options
class RowSpacing extends StatelessWidget {
const RowSpacing({super.key});
@override
Widget build(BuildContext context) {
return Column(
children: [
// 1. Using SizedBox
Row(
children: [
Container(width: 50, height: 50, color: Colors.red),
const SizedBox(width: 10),
Container(width: 50, height: 50, color: Colors.green),
const SizedBox(width: 20),
Container(width: 50, height: 50, color: Colors.blue),
],
),
const SizedBox(height: 10),
// 2. Using Spacer
Row(
children: [
Container(width: 50, height: 50, color: Colors.red),
const Spacer(),
Container(width: 50, height: 50, color: Colors.green),
const Spacer(),
Container(width: 50, height: 50, color: Colors.blue),
],
),
const SizedBox(height: 10),
// 3. Using MainAxisAlignment
Row(
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
children: [
Container(width: 50, height: 50, color: Colors.red),
Container(width: 50, height: 50, color: Colors.green),
Container(width: 50, height: 50, color: Colors.blue),
],
),
const SizedBox(height: 10),
// 4. Using Padding on children
Row(
children: [
Padding(
padding: const EdgeInsets.only(right: 10),
child: Container(width: 50, height: 50, color: Colors.red),
),
Padding(
padding: const EdgeInsets.only(right: 10),
child: Container(width: 50, height: 50, color: Colors.green),
),
Container(width: 50, height: 50, color: Colors.blue),
],
),
],
);
}
}
What's happening here? - SizedBox adds fixed spacing - Spacer distributes flexible space - MainAxisAlignment spaces automatically - Padding adds spacing around children
Real-World Examples
Common patterns using Row.
// 1. App Bar
class AppBarRow extends StatelessWidget {
const AppBarRow({super.key});
@override
Widget build(BuildContext context) {
return Container(
padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 8),
color: Colors.blue,
child: Row(
children: [
// Left: Back button
const Icon(Icons.arrow_back, color: Colors.white),
// Middle: Title
const Expanded(
child: Text(
'App Title',
style: TextStyle(
color: Colors.white,
fontSize: 20,
fontWeight: FontWeight.bold,
),
textAlign: TextAlign.center,
),
),
// Right: Action buttons
const Icon(Icons.search, color: Colors.white),
const SizedBox(width: 8),
const Icon(Icons.more_vert, color: Colors.white),
],
),
);
}
}
// 2. Card with icon and text
class CardWithRow extends StatelessWidget {
const CardWithRow({super.key});
@override
Widget build(BuildContext context) {
return Container(
padding: const EdgeInsets.all(16),
decoration: BoxDecoration(
color: Colors.white,
borderRadius: BorderRadius.circular(8),
boxShadow: [
BoxShadow(
color: Colors.black.withOpacity(0.1),
blurRadius: 8,
),
],
),
child: Row(
children: [
// Icon
Container(
width: 50,
height: 50,
decoration: const BoxDecoration(
color: Colors.blue,
shape: BoxShape.circle,
),
child: const Icon(
Icons.person,
color: Colors.white,
),
),
const SizedBox(width: 16),
// Text content
const Expanded(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
'John Doe',
style: TextStyle(
fontWeight: FontWeight.bold,
fontSize: 16,
),
),
Text(
'Flutter Developer',
style: TextStyle(
color: Colors.grey,
fontSize: 14,
),
),
],
),
),
// Action button
const Icon(Icons.chevron_right, color: Colors.grey),
],
),
);
}
}
// 3. Button row
class ButtonRow extends StatelessWidget {
const ButtonRow({super.key});
@override
Widget build(BuildContext context) {
return Row(
children: [
Expanded(
child: ElevatedButton(
onPressed: () {},
child: const Text('Cancel'),
),
),
const SizedBox(width: 8),
Expanded(
child: ElevatedButton(
onPressed: () {},
style: ElevatedButton.styleFrom(
backgroundColor: Colors.blue,
),
child: const Text('Confirm'),
),
),
],
);
}
}
// 4. Header with badge
class HeaderWithBadge extends StatelessWidget {
const HeaderWithBadge({super.key});
@override
Widget build(BuildContext context) {
return Row(
children: [
// Left: Label
const Text(
'Notifications',
style: TextStyle(
fontSize: 18,
fontWeight: FontWeight.bold,
),
),
const Spacer(),
// Right: Badge
Container(
padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 4),
decoration: BoxDecoration(
color: Colors.red,
borderRadius: BorderRadius.circular(12),
),
child: const Text(
'5 new',
style: TextStyle(
color: Colors.white,
fontSize: 12,
fontWeight: FontWeight.bold,
),
),
),
],
);
}
}
What's happening here? - App bar with left, center, right sections - Card with icon, text, and action - Button row with equal buttons - Header with badge counter
Best Practices
Use Expanded for Responsive Layout
// Good - Responsive
@override
Widget build(BuildContext context) {
return Row(
children: [
const Icon(Icons.star),
const SizedBox(width: 8),
Expanded(
child: Text('Long text that adapts'),
),
],
);
}
// Bad - Fixed width
@override
Widget build(BuildContext context) {
return Row(
children: [
const Icon(Icons.star),
const SizedBox(width: 8),
Container(
width: 200, // May overflow on smaller screens
child: const Text('Long text'),
),
],
);
}
Keep Row Children Consistent
// Good - Consistent heights
@override
Widget build(BuildContext context) {
return Row(
crossAxisAlignment: CrossAxisAlignment.center,
children: [
Container(height: 50, color: Colors.red),
Container(height: 50, color: Colors.green),
Container(height: 50, color: Colors.blue),
],
);
}
// Bad - Inconsistent heights
@override
Widget build(BuildContext context) {
return Row(
crossAxisAlignment: CrossAxisAlignment.center,
children: [
Container(height: 30, color: Colors.red),
Container(height: 60, color: Colors.green),
Container(height: 45, color: Colors.blue),
],
);
}
Use Appropriate Spacing
// Good - Using MainAxisAlignment
@override
Widget build(BuildContext context) {
return Row(
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
children: [
Icon(Icons.home),
Icon(Icons.search),
Icon(Icons.person),
],
);
}
// Bad - Hardcoded spacing
@override
Widget build(BuildContext context) {
return Row(
children: [
Icon(Icons.home),
const SizedBox(width: 20),
Icon(Icons.search),
const SizedBox(width: 20),
Icon(Icons.person),
],
);
}
Common Mistakes
Overusing Expanded
Wrong:
// All children expanded
Row(
children: [
Expanded(child: Text('Long text')),
Expanded(child: Text('Another text')),
Expanded(child: Text('Third text')),
],
)
Correct:
// Use flex factors
Row(
children: [
Expanded(
flex: 2,
child: Text('Long text'),
),
Expanded(
flex: 1,
child: Text('Another text'),
),
Expanded(
flex: 1,
child: Text('Third text'),
),
],
)
No Expanded for Long Text
Wrong:
// Text might overflow
Row(
children: [
Icon(Icons.star),
Text('Very long text that might not fit'),
],
)
Correct:
// Use Expanded for text
Row(
children: [
Icon(Icons.star),
Expanded(
child: Text('Very long text that will fit'),
),
],
)
Summary
Row arranges children horizontally with control over alignment, spacing, and sizing. Use Expanded for responsive layouts, MainAxisAlignment for spacing, and CrossAxisAlignment for vertical alignment. Row is essential for building horizontal UI components.
Next Steps
Did You Know?
- Row and Column are both Flex widgets
- Row takes the height of its tallest child
- CrossAxisAlignment controls vertical alignment
- Expanded and Flexible create responsive layouts
- MainAxisAlignment supports 6 alignment options
- Row can be nested inside Row
- Rows support RTL text direction