Flutter Layout — Row, Column, Stack, and Arranging Widgets
In this tutorial, you will learn about Flutter Layout. We cover key concepts, practical examples, and best practices to help you master this topic.
Flutter layout widgets arrange other widgets on screen using rows, columns, stacks, and flexible sizing to create responsive interfaces that adapt to different screen sizes.
What Will You Learn
- Row and Column for linear layouts
- MainAxisAlignment and CrossAxisAlignment
- Expanded and Flexible for proportional sizing
- Stack and Positioned for overlapping layouts
- Container for decoration and padding
- SizedBox and AspectRatio for fixed sizing
- LayoutBuilder for Responsive Design
Why It Matters
Layout is the foundation of any user interface. Flutter's layout system is based on constraints: parent widgets tell their children how much space they have, and children decide how to use that space. Understanding this constraint-based system is essential for building UIs that look correct on different screen sizes and orientations. Unlike CSS, Flutter layouts are fully type-checked at compile time.
Real-World Use
The DodaTech Flutter app uses Expanded in a Row to create a three-column layout that adapts to screen width. The Stack widget is used for overlaying a play button on top of a course thumbnail image. LayoutBuilder detects the available width and switches between a side-by-side and stacked layout on small screens.
Learning Path
flowchart LR A[Flutter Widgets] --> B[Flutter Layout\nYou are here] B --> C[Flutter Scrolling] style B fill:#f90,color:#fff
Row and Column
Row and Column arrange children horizontally or vertically:
import 'package:flutter/material.dart';
class RowColumnExample extends StatelessWidget {
const RowColumnExample({super.key});
@override
Widget build(BuildContext context) {
return Column(
// Vertical arrangement
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
// Horizontal alignment of children
crossAxisAlignment: CrossAxisAlignment.center,
children: [
Text('Top', style: TextStyle(fontSize: 24)),
// Row inside Column
Row(
mainAxisAlignment: MainAxisAlignment.spaceAround,
children: [
Icon(Icons.star, size: 40, color: Colors.amber),
Icon(Icons.favorite, size: 40, color: Colors.red),
Icon(Icons.thumb_up, size: 40, color: Colors.blue),
],
),
Text('Bottom', style: TextStyle(fontSize: 24)),
],
);
}
}
MainAxisAlignment controls spacing along the main axis (vertical for Column, horizontal for Row). CrossAxisAlignment controls alignment on the cross axis. Values include start, center, end, spaceBetween, spaceAround, and spaceEvenly.
Expanded and Flexible
Expanded and Flexible divide available space among children:
import 'package:flutter/material.dart';
class ExpandedExample extends StatelessWidget {
const ExpandedExample({super.key});
@override
Widget build(BuildContext context) {
return Row(
children: [
Expanded(
flex: 2,
child: Container(
color: Colors.red,
child: Center(child: Text('2/6', style: TextStyle(color: Colors.white))),
),
),
Expanded(
flex: 1,
child: Container(
color: Colors.green,
child: Center(child: Text('1/6', style: TextStyle(color: Colors.white))),
),
),
Expanded(
flex: 3,
child: Container(
color: Colors.blue,
child: Center(child: Text('3/6', style: TextStyle(color: Colors.white))),
),
),
],
);
}
}
Expanded forces the child to fill the available space. Flexible allows the child to be smaller than the available space. The flex value determines the proportion of space each child receives.
Stack and Positioned
Stack layers children on top of each other. Positioned places children at specific offsets:
import 'package:flutter/material.dart';
class StackExample extends StatelessWidget {
const StackExample({super.key});
@override
Widget build(BuildContext context) {
return SizedBox(
width: 300,
height: 200,
child: Stack(
children: [
// Base layer
Container(
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(16),
gradient: LinearGradient(
colors: [Colors.blue, Colors.purple],
),
),
),
// Centered text
Center(
child: Text(
'Flutter Stack',
style: TextStyle(
color: Colors.white,
fontSize: 24,
fontWeight: FontWeight.bold,
),
),
),
// Positioned badge
Positioned(
top: 8,
right: 8,
child: Container(
padding: EdgeInsets.symmetric(horizontal: 8, vertical: 4),
decoration: BoxDecoration(
color: Colors.red,
borderRadius: BorderRadius.circular(12),
),
child: Text(
'NEW',
style: TextStyle(
color: Colors.white,
fontSize: 12,
fontWeight: FontWeight.bold,
),
),
),
),
// Positioned at bottom
Positioned(
bottom: 8,
left: 8,
right: 8,
child: LinearProgressIndicator(
value: 0.7,
backgroundColor: Colors.white24,
valueColor: AlwaysStoppedAnimation(Colors.green),
),
),
],
),
);
}
}
The first child in Stack is drawn first. Positioned widgets use top, bottom, left, right offsets from the stack edges. Use Positioned.fill to stretch a child to fill the stack.
Container
Container is a versatile layout widget that combines padding, margins, decoration, and sizing:
Container(
margin: EdgeInsets.all(16),
padding: EdgeInsets.symmetric(horizontal: 24, vertical: 12),
decoration: BoxDecoration(
color: Colors.white,
borderRadius: BorderRadius.circular(12),
boxShadow: [
BoxShadow(
color: Colors.black.withOpacity(0.1),
blurRadius: 8,
offset: Offset(0, 4),
),
],
border: Border.all(
color: Colors.grey.shade300,
width: 1,
),
),
child: Text('Decorated Container'),
)
Container is convenient but can be overused. For simple padding, use Padding. For sizing, use SizedBox. Container creates a RenderObject even when it does nothing, so prefer specialized widgets for performance.
Alignment and Padding
Control the position and spacing of child widgets:
class AlignmentExample extends StatelessWidget {
const AlignmentExample({super.key});
@override
Widget build(BuildContext context) {
return Column(
children: [
// Align widget
Align(
alignment: Alignment.topRight,
child: Icon(Icons.star, size: 40),
),
SizedBox(height: 16),
// Padding
Padding(
padding: EdgeInsets.only(left: 32, right: 8),
child: Text('Indented text with left padding'),
),
SizedBox(height: 16),
// Center
Center(
child: Text('Centered text'),
),
SizedBox(height: 16),
// AspectRatio
AspectRatio(
aspectRatio: 16 / 9,
child: Container(
color: Colors.blue.shade100,
child: Center(child: Text('16:9 Box')),
),
),
],
);
}
}
Align positions a child within its bounds. Padding adds empty space around a child. Center is shorthand for Align(alignment: Alignment.center). AspectRatio forces a specific width-to-height ratio.
LayoutBuilder
LayoutBuilder adapts the layout based on the available constraints:
class ResponsiveLayout extends StatelessWidget {
const ResponsiveLayout({super.key});
@override
Widget build(BuildContext context) {
return LayoutBuilder(
builder: (context, constraints) {
if (constraints.maxWidth > 600) {
// Wide layout: side by side
return Row(
children: [
Expanded(
child: Card(
child: Padding(
padding: EdgeInsets.all(24),
child: Text('Panel 1', style: TextStyle(fontSize: 24)),
),
),
),
SizedBox(width: 16),
Expanded(
child: Card(
child: Padding(
padding: EdgeInsets.all(24),
child: Text('Panel 2', style: TextStyle(fontSize: 24)),
),
),
),
],
);
} else {
// Narrow layout: stacked vertically
return Column(
children: [
Card(
child: Padding(
padding: EdgeInsets.all(24),
child: Text('Panel 1', style: TextStyle(fontSize: 24)),
),
),
SizedBox(height: 16),
Card(
child: Padding(
padding: EdgeInsets.all(24),
child: Text('Panel 2', style: TextStyle(fontSize: 24)),
),
),
],
);
}
},
);
}
}
LayoutBuilder provides maxWidth and maxHeight from the parent constraints. Use it to switch between different layouts at breakpoints. This is Flutter's equivalent of CSS media queries.
IntrinsicHeight and IntrinsicWidth
These widgets size themselves to their intrinsic content size:
Row(
children: [
IntrinsicHeight(
child: Container(
color: Colors.blue.shade100,
padding: EdgeInsets.all(16),
child: Column(
children: [
Text('Short'),
Text('Content'),
],
),
),
),
SizedBox(width: 8),
IntrinsicHeight(
child: Container(
color: Colors.green.shade100,
padding: EdgeInsets.all(16),
child: Column(
children: [
Text('Taller'),
Text('Content'),
Text('Here'),
Text('More lines'),
],
),
),
),
],
)
Without IntrinsicHeight, the two containers would have independent heights. With it, both containers share the height of the tallest child. Use these widgets sparingly as they require two layout passes.
Flex and Wrap
Wrap flows children to the next line when there is not enough space:
class WrapExample extends StatelessWidget {
const WrapExample({super.key});
@override
Widget build(BuildContext context) {
return Wrap(
spacing: 8,
runSpacing: 4,
alignment: WrapAlignment.center,
children: [
Chip(label: Text('Dart')),
Chip(label: Text('Flutter')),
Chip(label: Text('Widgets')),
Chip(label: Text('Layout')),
Chip(label: Text('State Management')),
Chip(label: Text('Navigation')),
Chip(label: Text('Networking')),
Chip(label: Text('Animations')),
Chip(label: Text('Testing')),
Chip(label: Text('Deployment')),
],
);
}
}
Wrap is ideal for tag clouds, filter chips, and any layout where children may overflow the available width. It automatically wraps to the next line.
Common Mistakes
Using Column inside SingleChildScrollView without Expanded: A Column inside a scroll view does not constrain its height. The column can grow unbounded, causing layout errors. Use
ExpandedorConstrainedBoxto limit height.Overusing Container when simpler widgets suffice: Container is heavy. Use
SizedBoxfor fixed sizes,Paddingfor padding,DecoratedBoxfor decoration, andColoredBoxfor background colors.Forgetting overflow handling: Overflowing content causes yellow-black striped error indicators in debug mode. Use
Flexible,Expanded, orSingleChildScrollViewto handle overflow.Mixing mainAxisAlignment and crossAxisAlignment: The main axis direction depends on the widget (vertical for Column, horizontal for Row). Confusing the two produces unexpected layout.
Not testing on different screen sizes: A layout that works on a Pixel 6 may break on a tablet or small phone. Use
LayoutBuilderand test on multiple devices.
Practice Questions
- What is the difference between Expanded and Flexible?
- How does MainAxisAlignment differ from CrossAxisAlignment?
- When would you use Stack instead of Column or Row?
- How does LayoutBuilder help build responsive layouts?
- Challenge: Build a product card layout with an image on top, title and description in the middle, and a price and button row at the bottom. Use Stack for a discount badge overlay. Make it responsive to screen width.
Mini Project
Build a responsive dashboard layout:
- Header with app title and user avatar
- Two-panel layout: sidebar navigation and main content
- On narrow screens (width < 600), hide sidebar and show drawer
- Use LayoutBuilder to switch between layouts
- Use Stack for overlapping profile image
- Use Expanded for proportional panel sizing
FAQ
What is Next
Now that you understand layout, learn about scrolling widgets. Proceed to Flutter Scrolling for ListView, GridView, and scrollable content. Then explore State Management in Flutter for managing app state.
Built by the developers of DodaTech
Doda Browser, DodaZIP & Durga Antivirus Pro