Column
Understand how to use Column for vertical layout.
What is it?
Column is a widget that displays its children in a vertical array. It's one of the most commonly used layout widgets in Flutter. Column arranges widgets from top to bottom and can control how they align, distribute space, and size themselves. It is the vertical equivalent of the Row widget.
Why does it exist?
Column exists to:
- Arrange widgets vertically
- Create responsive vertical layouts
- Control alignment and spacing
- Distribute available space
- Support scrolling when needed
- Build complex UI components
- Enable flexible and adaptive designs
Basic Column
Column arranges children vertically.
// Basic Column usage
class BasicColumn extends StatelessWidget {
const BasicColumn({super.key});
@override
Widget build(BuildContext context) {
return Column(
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,
),
],
);
}
}
// Column properties:
// 1. children - List of widgets
// 2. mainAxisAlignment - Vertical alignment
// 3. crossAxisAlignment - Horizontal alignment
// 4. mainAxisSize - Min or max space
// 5. verticalDirection - Top to bottom or bottom to top
// 6. textBaseline - Text baseline
What's happening here? - Children are arranged top to bottom - Each child has fixed size - Column takes the width of widest child - Height is sum of children + spacing - Can control alignment and sizing
MainAxisAlignment
MainAxisAlignment controls vertical alignment.
// MainAxisAlignment examples
class ColumnMainAxisAlignment extends StatelessWidget {
const ColumnMainAxisAlignment({super.key});
@override
Widget build(BuildContext context) {
return Column(
children: [
// 1. Start - Top aligned
_buildColumnWithTitle(
'Start',
mainAxisAlignment: MainAxisAlignment.start,
),
// 2. Center - Center aligned
_buildColumnWithTitle(
'Center',
mainAxisAlignment: MainAxisAlignment.center,
),
// 3. End - Bottom aligned
_buildColumnWithTitle(
'End',
mainAxisAlignment: MainAxisAlignment.end,
),
// 4. SpaceBetween - Space between
_buildColumnWithTitle(
'SpaceBetween',
mainAxisAlignment: MainAxisAlignment.spaceBetween,
),
// 5. SpaceAround - Space around
_buildColumnWithTitle(
'SpaceAround',
mainAxisAlignment: MainAxisAlignment.spaceAround,
),
// 6. SpaceEvenly - Even space
_buildColumnWithTitle(
'SpaceEvenly',
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
),
],
);
}
Widget _buildColumnWithTitle(
String title, {
required MainAxisAlignment mainAxisAlignment,
}) {
return Container(
height: 150,
color: Colors.grey[200],
margin: const EdgeInsets.only(bottom: 8),
child: Column(
mainAxisAlignment: mainAxisAlignment,
children: [
Container(width: 40, height: 30, color: Colors.red),
Container(width: 40, height: 30, color: Colors.green),
Container(width: 40, height: 30, color: Colors.blue),
],
),
);
}
}
What's happening here? - start aligns children to the top - center centers children vertically - end aligns children to the bottom - spaceBetween distributes space between - spaceAround distributes space around - spaceEvenly distributes space evenly
CrossAxisAlignment
CrossAxisAlignment controls horizontal alignment.
// CrossAxisAlignment examples
class ColumnCrossAxisAlignment extends StatelessWidget {
const ColumnCrossAxisAlignment({super.key});
@override
Widget build(BuildContext context) {
return Container(
width: 200,
color: Colors.grey[200],
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Container(width: 30, height: 30, color: Colors.red),
Container(width: 60, height: 30, color: Colors.green),
Container(width: 90, height: 30, color: Colors.blue),
],
),
);
}
}
// CrossAxisAlignment options:
// 1. CrossAxisAlignment.start - Align to left
// 2. CrossAxisAlignment.center - Align to center
// 3. CrossAxisAlignment.end - Align to right
// 4. CrossAxisAlignment.stretch - Stretch to fill
// 5. CrossAxisAlignment.baseline - Align by text baseline
What's happening here? - start aligns children to the left - center centers children horizontally - end aligns children to the right - stretch stretches children to fill width - baseline aligns by text baseline
MainAxisSize
MainAxisSize controls how much space Column takes.
// MainAxisSize examples
class ColumnMainAxisSize extends StatelessWidget {
const ColumnMainAxisSize({super.key});
@override
Widget build(BuildContext context) {
return Column(
children: [
// 1. Min - Takes minimum space
Container(
color: Colors.grey[200],
child: Column(
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(
height: 200,
color: Colors.grey[200],
child: Column(
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: Column is as tall as children + spacing
// Max: Column expands to fill parent height
// Default is MainAxisSize.max
// Use min for tight wrapping, max for full height
What's happening here? - min takes minimum space needed - max expands to fill available space - Default is max - min is useful for tight layouts
Column with Expanded
Expanded makes children fill available space.
// Column with Expanded children
class ColumnWithExpanded extends StatelessWidget {
const ColumnWithExpanded({super.key});
@override
Widget build(BuildContext context) {
return Container(
height: 300,
color: Colors.grey[200],
child: Column(
children: [
// 1. Equal distribution
Expanded(
child: Container(
width: double.infinity,
color: Colors.red,
child: const Center(child: Text('1')),
),
),
Expanded(
child: Container(
width: double.infinity,
color: Colors.green,
child: const Center(child: Text('1')),
),
),
Expanded(
child: Container(
width: double.infinity,
color: Colors.blue,
child: const Center(child: Text('1')),
),
),
],
),
);
}
}
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
Real-World Examples
Common patterns using Column.
// 1. Profile card
class ProfileCard extends StatelessWidget {
const ProfileCard({super.key});
@override
Widget build(BuildContext context) {
return Card(
margin: const EdgeInsets.all(16),
child: Padding(
padding: const EdgeInsets.all(16),
child: Column(
crossAxisAlignment: CrossAxisAlignment.center,
children: [
const CircleAvatar(
radius: 50,
backgroundImage: NetworkImage('https://example.com/avatar.jpg'),
),
const SizedBox(height: 12),
const Text(
'John Doe',
style: TextStyle(
fontSize: 24,
fontWeight: FontWeight.bold,
),
),
const SizedBox(height: 4),
Text(
'Flutter Developer',
style: TextStyle(
color: Colors.grey[600],
fontSize: 16,
),
),
const SizedBox(height: 16),
Row(
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
children: [
_buildStat('Posts', '150'),
_buildStat('Followers', '2.5K'),
_buildStat('Following', '350'),
],
),
const SizedBox(height: 16),
SizedBox(
width: double.infinity,
child: ElevatedButton(
onPressed: () {},
child: const Text('Follow'),
),
),
],
),
),
);
}
Widget _buildStat(String label, String value) {
return Column(
children: [
Text(
value,
style: const TextStyle(
fontSize: 18,
fontWeight: FontWeight.bold,
),
),
Text(
label,
style: TextStyle(
color: Colors.grey[600],
fontSize: 14,
),
),
],
);
}
}
// 2. Login form
class LoginForm extends StatelessWidget {
const LoginForm({super.key});
@override
Widget build(BuildContext context) {
return Padding(
padding: const EdgeInsets.all(16),
child: Column(
crossAxisAlignment: CrossAxisAlignment.stretch,
children: [
const Text(
'Welcome Back',
style: TextStyle(
fontSize: 28,
fontWeight: FontWeight.bold,
),
textAlign: TextAlign.center,
),
const SizedBox(height: 8),
Text(
'Sign in to continue',
style: TextStyle(
color: Colors.grey[600],
fontSize: 16,
),
textAlign: TextAlign.center,
),
const SizedBox(height: 32),
const TextField(
decoration: InputDecoration(
labelText: 'Email',
border: OutlineInputBorder(),
prefixIcon: Icon(Icons.email),
),
),
const SizedBox(height: 16),
const TextField(
decoration: InputDecoration(
labelText: 'Password',
border: OutlineInputBorder(),
prefixIcon: Icon(Icons.lock),
),
obscureText: true,
),
const SizedBox(height: 24),
ElevatedButton(
onPressed: () {},
style: ElevatedButton.styleFrom(
padding: const EdgeInsets.symmetric(vertical: 16),
),
child: const Text('Sign In'),
),
const SizedBox(height: 16),
TextButton(
onPressed: () {},
child: const Text('Forgot Password?'),
),
],
),
);
}
}
// 3. Article card
class ArticleCard extends StatelessWidget {
const ArticleCard({super.key});
@override
Widget build(BuildContext context) {
return Card(
margin: const EdgeInsets.all(8),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
// Image
Container(
height: 200,
decoration: const BoxDecoration(
image: DecorationImage(
image: NetworkImage('https://example.com/image.jpg'),
fit: BoxFit.cover,
),
borderRadius: BorderRadius.only(
topLeft: Radius.circular(4),
topRight: Radius.circular(4),
),
),
),
// Content
Padding(
padding: const EdgeInsets.all(16),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
'Article Title',
style: const TextStyle(
fontSize: 20,
fontWeight: FontWeight.bold,
),
),
const SizedBox(height: 8),
Text(
'Lorem ipsum dolor sit amet, consectetur adipiscing elit. '
'Sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.',
style: TextStyle(
color: Colors.grey[600],
fontSize: 14,
height: 1.5,
),
),
const SizedBox(height: 16),
Row(
children: [
const CircleAvatar(
radius: 16,
backgroundImage: NetworkImage('https://example.com/avatar.jpg'),
),
const SizedBox(width: 8),
Text(
'John Doe',
style: TextStyle(
color: Colors.grey[700],
fontSize: 14,
),
),
const Spacer(),
Text(
'2 hours ago',
style: TextStyle(
color: Colors.grey[500],
fontSize: 12,
),
),
],
),
],
),
),
],
),
);
}
}
What's happening here? - Profile card with avatar and stats - Login form with fields and buttons - Article card with image and content - Common real-world patterns
Best Practices
Use Expanded for Responsive Layout
// Good - Responsive
@override
Widget build(BuildContext context) {
return Column(
children: [
const Text('Header'),
Expanded(
child: Container(
color: Colors.blue,
child: const Text('Content that fills space'),
),
),
const Text('Footer'),
],
);
}
// Bad - Fixed height
@override
Widget build(BuildContext context) {
return Column(
children: [
const Text('Header'),
Container(
height: 200, // Fixed height may overflow
color: Colors.blue,
child: const Text('Content'),
),
const Text('Footer'),
],
);
}
Keep Column Children Consistent
// Good - Consistent widths
@override
Widget build(BuildContext context) {
return Column(
crossAxisAlignment: CrossAxisAlignment.stretch,
children: [
Container(height: 50, color: Colors.red),
Container(height: 50, color: Colors.green),
Container(height: 50, color: Colors.blue),
],
);
}
// Bad - Inconsistent widths
@override
Widget build(BuildContext context) {
return Column(
crossAxisAlignment: CrossAxisAlignment.center,
children: [
Container(width: 30, height: 50, color: Colors.red),
Container(width: 60, height: 50, color: Colors.green),
Container(width: 90, height: 50, color: Colors.blue),
],
);
}
Use Appropriate Spacing
// Good - Using MainAxisAlignment
@override
Widget build(BuildContext context) {
return Column(
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
children: [
Icon(Icons.home),
Icon(Icons.search),
Icon(Icons.person),
],
);
}
// Bad - Hardcoded spacing
@override
Widget build(BuildContext context) {
return Column(
children: [
Icon(Icons.home),
const SizedBox(height: 20),
Icon(Icons.search),
const SizedBox(height: 20),
Icon(Icons.person),
],
);
}
Common Mistakes
Overusing Expanded
Wrong:
// All children expanded
Column(
children: [
Expanded(child: Container(color: Colors.red)),
Expanded(child: Container(color: Colors.green)),
Expanded(child: Container(color: Colors.blue)),
],
)
Correct:
// Use flex factors
Column(
children: [
Expanded(
flex: 2,
child: Container(color: Colors.red),
),
Expanded(
flex: 1,
child: Container(color: Colors.green),
),
Expanded(
flex: 1,
child: Container(color: Colors.blue),
),
],
)
No Expanded for Long Content
Wrong:
// Content might overflow
Column(
children: [
Text('Very long text that might not fit'),
Text('Another long text'),
],
)
Correct:
// Use Expanded for content
Column(
children: [
Expanded(
child: Text('Very long text that will fit'),
),
Expanded(
child: Text('Another long text that will fit'),
),
],
)
Summary
Column arranges widgets vertically with control over alignment, spacing, and sizing. Use Expanded for responsive layouts, MainAxisAlignment for spacing, and CrossAxisAlignment for horizontal alignment. Column is essential for building vertical UI components.
Next Steps
Did You Know?
- Column and Row are both Flex widgets
- Column takes the width of its widest child
- CrossAxisAlignment controls horizontal alignment
- Expanded and Flexible create responsive layouts
- MainAxisAlignment supports 6 alignment options
- Column can be nested inside Column
- Column supports scrolling when wrapped in SingleChildScrollView