Creating Flexible and Responsive Layouts in Flutter
Flexible and responsive layouts are essential in Flutter applications because the same UI may need to work on different mobile phones, tablets, desktop screens, and web browsers. Flutter provides powerful layout widgets such as Row, Column, Expanded, Flexible, Wrap, LayoutBuilder, and MediaQuery to create layouts that automatically adapt to available screen space.
Flutter's Expanded and Flexible widgets are specifically designed for distributing available space inside Row, Column, and Flex. Expanded forces its child to fill the allocated space, while Flexible allows the child to use less than the allocated space when appropriate.
1. What Is a Flexible Layout?
A flexible layout is a layout that can adjust its size and position according to the available space. Instead of giving every widget a fixed width or height, Flutter can distribute available space dynamically.
For example, consider a screen containing two cards. On a wide screen, the cards can appear side by side. On a narrow screen, the cards can be displayed one below another.
Wide Screen:
+-------------------+-------------------+
| Card 1 | Card 2 |
+-------------------+-------------------+
Small Screen:
+---------------------------------------+
| Card 1 |
+---------------------------------------+
| Card 2 |
+---------------------------------------+
2. What Is a Responsive Layout?
A responsive layout changes its structure, size, spacing, or alignment based on the available screen dimensions.
A responsive Flutter application should be able to provide a usable interface on:
- Small mobile phones
- Large mobile phones
- Tablets
- Laptops
- Desktop computers
- Flutter web browsers
3. Flexible vs Responsive Layout
| Flexible Layout | Responsive Layout |
| Uses available space efficiently. | Adapts the UI to different screen sizes. |
| Commonly uses Row, Column, Expanded and Flexible. | Can use LayoutBuilder, MediaQuery, Wrap and responsive breakpoints. |
| Focuses on space distribution. | Focuses on changing the UI according to screen size. |
| Useful inside individual sections. | Useful for complete application screens. |
4. Understanding Flutter's Flex Layout System
Flutter uses the Flex layout system for arranging widgets in one direction. Row is a horizontal Flex layout, while Column is a vertical Flex layout.
Flex
├── Row → Horizontal layout
└── Column → Vertical layout
The Flex system distributes available space among its children according to properties such as flex, mainAxisAlignment, crossAxisAlignment, and mainAxisSize.
5. Using Expanded for Flexible Width
Expanded makes a child fill the available space along the main axis of a Row, Column, or Flex. Multiple Expanded children divide the remaining space according to their flex values.
Row(
children: [
Expanded(
child: Container(
height: 100,
color: Colors.blue,
),
),
Expanded(
child: Container(
height: 100,
color: Colors.green,
),
),
],
)
Both containers receive an equal share because both have the default flex: 1.
6. Using Flex Values
The flex property controls how available space is distributed between flexible children.
Row(
children: [
Expanded(
flex: 1,
child: Container(
height: 100,
color: Colors.blue,
),
),
Expanded(
flex: 2,
child: Container(
height: 100,
color: Colors.green,
),
),
],
)
In this example, the available width is divided into three proportional parts:
- First container = 1 part
- Second container = 2 parts
- Total = 3 parts
Therefore, the second container receives approximately twice the width of the first container.
7. Expanded Uses Tight Flex
Expanded uses FlexFit.tight. This means the child is required to fill the space allocated to it.
Expanded(
flex: 1,
child: Container(
color: Colors.blue,
child: const Center(
child: Text('Expanded'),
),
),
)
This behavior is useful when you want UI sections to completely occupy the available space.
8. Using Flexible
Flexible allows a child inside a Row, Column, or Flex to use available space without forcing it to fill the entire allocated area.
Row(
children: [
Flexible(
child: Container(
padding: const EdgeInsets.all(16),
color: Colors.blue,
child: const Text(
'This text can use available space.',
),
),
),
const SizedBox(width: 10),
const Icon(Icons.star),
],
)
By default, Flexible uses FlexFit.loose, allowing the child to be smaller than the allocated space.
9. Expanded vs Flexible
| Feature | Expanded | Flexible |
| Parent | Row, Column or Flex | Row, Column or Flex |
| Default flex | 1 | 1 |
| Default fit | FlexFit.tight | FlexFit.loose |
| Must fill allocated space? | Yes | No |
| Main purpose | Fill remaining space | Share available space while allowing smaller content |
10. Creating a Responsive Row
A common responsive pattern is to use Expanded for content that should automatically adjust its width.
Row(
children: [
Expanded(
child: Container(
padding: const EdgeInsets.all(20),
color: Colors.blue,
child: const Text(
'Left Section',
textAlign: TextAlign.center,
),
),
),
const SizedBox(width: 12),
Expanded(
child: Container(
padding: const EdgeInsets.all(20),
color: Colors.green,
child: const Text(
'Right Section',
textAlign: TextAlign.center,
),
),
),
],
)
The two sections automatically share the available horizontal space.
11. Creating a Responsive Column
Expanded can also distribute vertical space when used inside a Column.
Column(
children: [
const Text('Header'),
Expanded(
child: Container(
width: double.infinity,
color: Colors.blue,
child: const Center(
child: Text('Flexible Content Area'),
),
),
),
const Text('Footer'),
],
)
The middle section takes the remaining vertical space between the header and footer.
12. Using Spacer for Flexible Spacing
Spacer creates flexible empty space inside a Row, Column, or another Flex layout.
Row(
children: [
const Text('Profile'),
const Spacer(),
const Icon(Icons.settings),
],
)
The Spacer pushes the settings icon toward the right side of the available row.
13. Responsive Text Layout
Long text can cause overflow when placed inside a Row. Wrapping the text with Expanded or Flexible allows Flutter to constrain the text according to the available width.
Row(
children: [
const CircleAvatar(
child: Icon(Icons.person),
),
const SizedBox(width: 12),
Expanded(
child: Text(
'This is a long user name that needs to adapt to the available screen width.',
maxLines: 2,
overflow: TextOverflow.ellipsis,
),
),
],
)
This is a common pattern for profile lists, chat applications, product cards, and notification items.
14. Responsive Buttons
Buttons can also use flexible layouts to prevent overflow.
Row(
children: [
Expanded(
child: ElevatedButton(
onPressed: () {},
child: const Text('Login'),
),
),
const SizedBox(width: 12),
Expanded(
child: OutlinedButton(
onPressed: () {},
child: const Text('Register'),
),
),
],
)
15. Using Wrap for Responsive Items
Row keeps all children on one horizontal line. If there is not enough space, the content can overflow. Wrap can move children to another line when required.
Wrap(
spacing: 10,
runSpacing: 10,
children: [
Chip(label: Text('Flutter')),
Chip(label: Text('Dart')),
Chip(label: Text('Firebase')),
Chip(label: Text('UI Design')),
Chip(label: Text('Mobile')),
],
)
Wrap is useful for tags, chips, categories, filters, buttons, and responsive card layouts.
16. Row vs Wrap
| Row | Wrap |
| One horizontal line | Can create multiple lines |
| Can overflow if content is too large | Moves children to another run |
| Useful for toolbars and headers | Useful for tags, chips and responsive collections |
17. Using MediaQuery for Responsive Layouts
MediaQuery provides information about the current application window, including its size. It can be used when the UI needs to make decisions based on screen dimensions.
Widget build(BuildContext context) {
final screenWidth = MediaQuery.sizeOf(context).width;
return Scaffold(
body: Center(
child: Text(
screenWidth < 600
? 'Mobile Layout'
: 'Large Screen Layout',
),
),
);
}
18. Creating Breakpoints
Breakpoints are width ranges at which the application changes its layout structure.
Widget build(BuildContext context) {
final width = MediaQuery.sizeOf(context).width;
if (width < 600) {
return const MobileLayout();
}
if (width < 1024) {
return const TabletLayout();
}
return const DesktopLayout();
}
These values are examples. Breakpoints should be selected according to the content and design requirements of the application rather than blindly using device names.
19. Using LayoutBuilder
LayoutBuilder is useful when a widget needs to respond to the constraints supplied by its parent.
LayoutBuilder(
builder: (context, constraints) {
if (constraints.maxWidth < 600) {
return const Text('Small Layout');
}
return const Text('Large Layout');
},
)
This approach is particularly useful for reusable components because the component can respond to the space provided by its parent rather than relying only on the overall screen size.
20. Responsive Card Layout
Widget buildCardLayout(double width) {
if (width < 600) {
return Column(
children: [
buildCard('Card 1'),
const SizedBox(height: 12),
buildCard('Card 2'),
],
);
}
return Row(
children: [
Expanded(child: buildCard('Card 1')),
const SizedBox(width: 12),
Expanded(child: buildCard('Card 2')),
],
);
}
Widget buildCard(String title) {
return Card(
child: Padding(
padding: const EdgeInsets.all(20),
child: Text(title),
),
);
}
21. Complete Responsive Dashboard Example
import 'package:flutter/material.dart';
void main() {
runApp(const ResponsiveApp());
}
class ResponsiveApp extends StatelessWidget {
const ResponsiveApp({super.key});
@override
Widget build(BuildContext context) {
return MaterialApp(
debugShowCheckedModeBanner: false,
home: const ResponsiveDashboard(),
);
}
}
class ResponsiveDashboard extends StatelessWidget {
const ResponsiveDashboard({super.key});
Widget buildCard(String title, String value, IconData icon) {
return Card(
child: Padding(
padding: const EdgeInsets.all(20),
child: Row(
children: [
Icon(icon, size: 35),
const SizedBox(width: 15),
Expanded(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(title),
const SizedBox(height: 8),
Text(
value,
style: const TextStyle(
fontSize: 22,
fontWeight: FontWeight.bold,
),
),
],
),
),
],
),
),
);
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text('Responsive Dashboard'),
),
body: LayoutBuilder(
builder: (context, constraints) {
final isSmall = constraints.maxWidth < 600;
final cards = [
buildCard('Users', '1,250', Icons.people),
buildCard('Orders', '840', Icons.shopping_cart),
buildCard('Revenue', '₹85,000', Icons.currency_rupee),
buildCard('Pending', '24', Icons.pending),
];
return SingleChildScrollView(
padding: const EdgeInsets.all(16),
child: isSmall
? Column(
children: [
for (final card in cards) ...[
card,
const SizedBox(height: 12),
],
],
)
: Column(
children: [
Row(
children: [
Expanded(child: cards[0]),
const SizedBox(width: 12),
Expanded(child: cards[1]),
],
),
const SizedBox(height: 12),
Row(
children: [
Expanded(child: cards[2]),
const SizedBox(width: 12),
Expanded(child: cards[3]),
],
),
],
),
);
},
),
);
}
}
22. Responsive Login Form
LayoutBuilder(
builder: (context, constraints) {
final isMobile = constraints.maxWidth < 600;
return Center(
child: SizedBox(
width: isMobile ? double.infinity : 450,
child: Card(
child: Padding(
padding: const EdgeInsets.all(24),
child: Column(
mainAxisSize: MainAxisSize.min,
children: [
const Text(
'Login',
style: TextStyle(
fontSize: 28,
fontWeight: FontWeight.bold,
),
),
const SizedBox(height: 20),
const TextField(
decoration: InputDecoration(
labelText: 'Email',
border: OutlineInputBorder(),
),
),
const SizedBox(height: 15),
const TextField(
obscureText: true,
decoration: InputDecoration(
labelText: 'Password',
border: OutlineInputBorder(),
),
),
const SizedBox(height: 20),
SizedBox(
width: double.infinity,
child: ElevatedButton(
onPressed: () {},
child: const Text('Login'),
),
),
],
),
),
),
),
);
},
)
23. Responsive Navigation Example
A common responsive application pattern is to display a sidebar on larger screens and a simpler navigation control on smaller screens.
LayoutBuilder(
builder: (context, constraints) {
final isDesktop = constraints.maxWidth >= 900;
if (isDesktop) {
return Row(
children: [
SizedBox(
width: 250,
child: NavigationRail(
destinations: const [
NavigationRailDestination(
icon: Icon(Icons.home),
label: Text('Home'),
),
NavigationRailDestination(
icon: Icon(Icons.settings),
label: Text('Settings'),
),
],
selectedIndex: 0,
onDestinationSelected: (index) {},
),
),
const Expanded(
child: Center(
child: Text('Desktop Content'),
),
),
],
);
}
return const Center(
child: Text('Mobile Content'),
);
},
)
24. Responsive Image Layout
Images should generally be constrained instead of assigning unnecessarily large fixed dimensions.
Expanded(
child: Image.network(
'https://example.com/image.jpg',
fit: BoxFit.cover,
),
)
Inside a responsive layout, Expanded can provide the available width while BoxFit.cover controls how the image is fitted within its constraints.
25. Flexible with Long Text
Row(
children: [
const Icon(Icons.info),
const SizedBox(width: 8),
Flexible(
child: Text(
'This is a long message that should not cause horizontal overflow on smaller screens.',
softWrap: true,
),
),
],
)
This pattern is useful for messages, descriptions, notification text, product titles, and list items.
26. Using MainAxisSize for Compact Layouts
MainAxisSize.min allows a Row or Column to take only the space required by its children, subject to the incoming constraints.
Column(
mainAxisSize: MainAxisSize.min,
children: [
const Text('Title'),
const SizedBox(height: 8),
ElevatedButton(
onPressed: () {},
child: const Text('Continue'),
),
],
)
MainAxisSize.max attempts to occupy the maximum available main-axis space, while MainAxisSize.min minimizes the amount of main-axis space used.
27. Avoiding Fixed Widths Everywhere
A common beginner mistake is assigning fixed widths to every widget.
// Less flexible
Container(
width: 350,
child: const Text('Content'),
)
A more flexible approach is to allow the parent layout to determine the available width.
Expanded(
child: Container(
padding: const EdgeInsets.all(16),
child: const Text('Content'),
),
)
Fixed dimensions are still useful when the design genuinely requires a fixed size, but they should not be used unnecessarily for every element.
28. Responsive Spacing
Spacing can also be adjusted based on available width.
LayoutBuilder(
builder: (context, constraints) {
final spacing = constraints.maxWidth < 600 ? 12.0 : 24.0;
return Padding(
padding: EdgeInsets.all(spacing),
child: const Text('Responsive Content'),
);
},
)
29. Responsive Grid Using GridView
A grid is useful for dashboards, product catalogs, galleries, and card-based interfaces.
GridView.builder(
padding: const EdgeInsets.all(16),
gridDelegate: const SliverGridDelegateWithMaxCrossAxisExtent(
maxCrossAxisExtent: 300,
crossAxisSpacing: 16,
mainAxisSpacing: 16,
childAspectRatio: 1.3,
),
itemCount: 10,
itemBuilder: (context, index) {
return Card(
child: Center(
child: Text('Item ${index + 1}'),
),
);
},
)
Using a maximum cross-axis extent allows the number of columns to adjust as more horizontal space becomes available.
30. Flexible Layout with Nested Row and Column
Row(
children: [
Expanded(
flex: 1,
child: Container(
padding: const EdgeInsets.all(16),
color: Colors.blue,
child: const Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
'Profile',
style: TextStyle(
color: Colors.white,
fontSize: 22,
),
),
SizedBox(height: 10),
Text(
'User information',
style: TextStyle(color: Colors.white),
),
],
),
),
),
const SizedBox(width: 16),
Expanded(
flex: 2,
child: Container(
padding: const EdgeInsets.all(16),
color: Colors.green,
child: const Text(
'Main content area',
style: TextStyle(color: Colors.white),
),
),
),
],
)
31. Handling Small Screens
When a layout becomes too narrow, simply shrinking every widget may make the UI difficult to use. Instead, change the layout structure.
Widget buildResponsiveLayout(BuildContext context) {
return LayoutBuilder(
builder: (context, constraints) {
if (constraints.maxWidth < 600) {
return const Column(
children: [
Text('Mobile Header'),
Text('Mobile Content'),
],
);
}
return const Row(
children: [
Expanded(child: Text('Desktop Header')),
Expanded(child: Text('Desktop Content')),
],
);
},
);
}
32. Flexible Layout with Scrollable Content
When content can become larger than the available screen height, a scrolling widget such as ListView or SingleChildScrollView may be appropriate.
SingleChildScrollView(
padding: const EdgeInsets.all(16),
child: Column(
children: [
const Text('Header'),
const SizedBox(height: 20),
Container(height: 300, color: Colors.blue),
const SizedBox(height: 20),
Container(height: 300, color: Colors.green),
],
),
)
Be careful when placing Expanded or Flexible inside a vertically scrolling viewport. Such a viewport can provide unbounded vertical space, which can make flex-based remaining-space calculations invalid.
33. Common Expanded/Flexible Error
SingleChildScrollView(
child: Column(
children: [
Expanded(
child: Container(),
),
],
),
)
The above pattern can produce an unbounded-height flex error because the scroll view does not provide a finite maximum height in its scrolling direction.
A common solution is to remove the unnecessary Expanded and allow the content to determine its natural height, or restructure the layout so that the flex widget receives a finite constraint.
34. Flexible Layout Best Practices
- Use
Expanded when a child should fill its allocated remaining space.
- Use
Flexible when the child should be allowed to remain smaller.
- Use
Wrap when children may need to move onto multiple lines.
- Use
LayoutBuilder when a reusable widget should respond to its parent's constraints.
- Use
MediaQuery.sizeOf(context) when the overall application window size is relevant.
- Prefer flexible constraints over unnecessary fixed dimensions.
- Use scrolling widgets when content can exceed the available space.
- Test layouts at different widths and heights.
- Use meaningful breakpoints based on content requirements.
- Keep text readable on both small and large screens.
35. Common Mistakes
Mistake 1: Putting Expanded Outside Row or Column
Expanded(
child: Text('Incorrect usage'),
)
Expanded should be a descendant of a suitable Flex widget such as Row, Column, or Flex.
Mistake 2: Using Too Many Fixed Widths
Large fixed widths can cause overflow on smaller screens.
Mistake 3: Using Row for Too Many Items
If several items cannot fit horizontally, consider Wrap, ListView, or a responsive layout change.
Mistake 4: Ignoring Text Overflow
Long text inside a Row should generally be constrained using Expanded or Flexible.
Mistake 5: Incorrect Expanded Inside ScrollView
Do not automatically use Expanded inside a vertically scrolling Column. First understand the constraints provided by the scrollable parent.
36. Flexible Layout Design Strategy
- Start with the smallest practical layout.
- Identify which widgets need to grow.
- Use Expanded or Flexible for flexible space.
- Use Wrap when items may require multiple lines.
- Define layout changes based on available width.
- Use LayoutBuilder for component-level responsiveness.
- Use MediaQuery when application-level dimensions are required.
- Add scrolling when content can exceed the available height.
- Test the layout on multiple screen sizes.
- Remove unnecessary fixed dimensions.
37. Real-World Applications
- Responsive login and registration screens
- Shopping and e-commerce applications
- Product grids
- Admin dashboards
- Profile pages
- Chat applications
- News applications
- Food delivery applications
- Banking dashboards
- Responsive web applications
- Tablet applications
- Desktop Flutter applications
38. Interview Questions
Q1. What is the purpose of Expanded?
Expanded makes a child of a Row, Column, or Flex fill the available space along the main axis.
Q2. What is the difference between Expanded and Flexible?
Expanded uses tight constraints and requires its child to fill the allocated space. Flexible uses loose constraints by default and allows the child to be smaller than the allocated space.
Q3. What is the flex property?
The flex property determines the proportional share of remaining space allocated to flexible children.
Q4. When should you use Wrap?
Use Wrap when children may need to move onto multiple rows or columns instead of remaining on one line.
Q5. What is LayoutBuilder?
LayoutBuilder provides the constraints available to its child and allows the UI to change according to those constraints.
Q6. Why can Expanded cause an error inside a ScrollView?
A scrollable viewport can provide unbounded space along its scrolling axis. Expanded needs a finite amount of remaining space to calculate its allocation.
Q7. What is responsive design?
Responsive design means adapting the application's layout and presentation to different available screen sizes and constraints.
39. Practice Exercises
- Create a Row containing three cards using Expanded.
- Create a responsive layout that changes from Row to Column below a chosen width.
- Create a profile screen using Flexible for long user information.
- Create a responsive login form using LayoutBuilder.
- Create a product grid that adapts the number of columns to available width.
- Create a dashboard that displays two columns on larger screens and one column on smaller screens.
- Create a responsive tag/chip layout using Wrap.
- Create a responsive navigation layout for mobile and desktop.
40. Quick Revision
| Widget/Concept | Purpose |
| Row | Horizontal Flex layout |
| Column | Vertical Flex layout |
| Flex | Custom one-dimensional layout |
| Expanded | Fills allocated remaining space |
| Flexible | Shares available space without requiring the child to fill it |
| Spacer | Creates flexible empty space |
| Wrap | Moves children onto additional runs |
| LayoutBuilder | Builds UI according to parent constraints |
| MediaQuery | Provides application/window dimension information |
| ListView | Provides efficient scrolling for lists |
| SingleChildScrollView | Allows a single child to scroll when required |
| MainAxisSize | Controls how much main-axis space a Flex occupies |
41. Useful Flutter Documentation
42. Flutter Training Resources
For structured Flutter learning, practical development training, and course information, visit the following resources:
43. Summary
Creating flexible and responsive layouts is an important Flutter skill. Row, Column, and Flex provide the basic one-dimensional layout system, while Expanded and Flexible help distribute available space. Wrap is useful when content needs to move across multiple lines. LayoutBuilder and MediaQuery can be used to make layout decisions based on available dimensions. Scrollable widgets should be introduced when content can exceed the available space.
A good responsive Flutter layout does not simply make everything smaller. Instead, it uses appropriate constraints, flexible widgets, spacing, wrapping, scrolling, and structural changes so that the interface remains usable across different screen sizes.
Key Formula: Flexible Flutter UI = Proper Constraints + Expanded/Flexible + Responsive Breakpoints + Wrap/Grid + Scrolling + Testing