Expanded & Flexible
Introduction to Flexible Widgets
When building layouts in Flutter, you often need children to share available space proportionally rather than using fixed sizes. The Expanded and Flexible widgets solve this problem by giving children the ability to grow and shrink within a Row, Column, or Flex parent.
Both widgets wrap a child and assign it a flex factor that determines how much of the remaining space it receives. The key difference lies in how they handle the allocated space.
The Expanded Widget
Expanded forces its child to fill all the remaining available space along the main axis. The child must take up exactly the space allocated to it -- no more, no less.
Basic Expanded Example
Row(
children: <Widget>[
Container(width: 80, height: 50, color: Colors.red),
Expanded(
child: Container(height: 50, color: Colors.green),
),
Container(width: 80, height: 50, color: Colors.blue),
],
)
// Red: 80px | Green: fills remaining | Blue: 80px
In this example, the red and blue containers have fixed widths of 80px each. The green container, wrapped in Expanded, takes all the remaining width. If the Row is 400px wide, the green container gets 400 - 80 - 80 = 240px.
The Flexible Widget
Flexible is similar to Expanded but gives the child the option to be smaller than the allocated space. The child can size itself up to the maximum allocated space but is not forced to fill it entirely.
Basic Flexible Example
Row(
children: <Widget>[
Flexible(
child: Container(
width: 100, // Only uses 100px even if more is available
height: 50,
color: Colors.orange,
),
),
Container(width: 80, height: 50, color: Colors.purple),
],
)
// Orange: up to allocated space but only 100px | Purple: 80px
With Flexible, the orange container requests 100px. If the allocated space is 320px, the container still only takes 100px -- the remaining 220px stays empty. With Expanded, it would have been forced to stretch to 320px.
The flex Factor
Both Expanded and Flexible accept a flex parameter (default: 1) that determines the proportion of remaining space each child receives. The space is divided according to the ratio of flex values.
Proportional Space with flex
Row(
children: <Widget>[
Expanded(
flex: 2,
child: Container(height: 50, color: Colors.red),
),
Expanded(
flex: 1,
child: Container(height: 50, color: Colors.green),
),
Expanded(
flex: 1,
child: Container(height: 50, color: Colors.blue),
),
],
)
// Total flex = 2 + 1 + 1 = 4
// Red: 2/4 = 50% | Green: 1/4 = 25% | Blue: 1/4 = 25%
flex: 3 and flex: 7 gives a 30%/70% split.FlexFit: Tight vs Loose
The fundamental difference between Expanded and Flexible comes down to the FlexFit property:
- FlexFit.tight (used by Expanded) -- The child must fill the allocated space completely. It is forced to be exactly the size of its allocation.
- FlexFit.loose (used by Flexible) -- The child can be smaller than the allocated space. It sizes itself naturally, up to the maximum allocation.
FlexFit.tight vs FlexFit.loose
// Expanded is shorthand for:
Flexible(
fit: FlexFit.tight,
child: Container(height: 50, color: Colors.red),
)
// Flexible defaults to:
Flexible(
fit: FlexFit.loose,
child: Container(height: 50, color: Colors.blue),
)
// You can make Flexible behave like Expanded:
Flexible(
fit: FlexFit.tight,
flex: 1,
child: Container(height: 50, color: Colors.green),
)
Expanded is literally a convenience wrapper around Flexible(fit: FlexFit.tight). They are functionally identical when using tight fit.The Spacer Widget
The Spacer widget is a convenience widget that creates an empty expanded space. It is equivalent to Expanded(child: SizedBox.shrink()). Spacer is useful for pushing children apart without creating visible elements.
Spacer Usage
Row(
children: <Widget>[
Text('Left', style: TextStyle(fontSize: 18)),
Spacer(), // Pushes 'Left' and 'Right' to opposite ends
Text('Right', style: TextStyle(fontSize: 18)),
],
)
// With flex factor:
Row(
children: <Widget>[
Text('Start'),
Spacer(flex: 2), // Twice the space
Text('Middle'),
Spacer(flex: 1), // Half the space
Text('End'),
],
)
Expanded(child: SizedBox()) and communicates intent better. Use it when you need flexible empty space between widgets.Distributing Space Proportionally
A common requirement is splitting a layout into proportional sections. Here is how to create common split ratios:
Common Split Ratios
// 50/50 split
Row(
children: [
Expanded(flex: 1, child: Container(color: Colors.red)),
Expanded(flex: 1, child: Container(color: Colors.blue)),
],
)
// 1/3 and 2/3 split
Row(
children: [
Expanded(flex: 1, child: Container(color: Colors.red)),
Expanded(flex: 2, child: Container(color: Colors.blue)),
],
)
// 25/50/25 split
Row(
children: [
Expanded(flex: 1, child: Container(color: Colors.red)),
Expanded(flex: 2, child: Container(color: Colors.green)),
Expanded(flex: 1, child: Container(color: Colors.blue)),
],
)
Combining Expanded with Fixed-Size Widgets
The most practical pattern is mixing fixed-size widgets with Expanded widgets. Flutter first allocates space to fixed-size children, then distributes the remaining space among Expanded/Flexible children based on their flex factors.
Fixed + Expanded Layout
Row(
children: <Widget>[
// Fixed: avatar (56px)
CircleAvatar(radius: 28, child: Icon(Icons.person)),
SizedBox(width: 12),
// Flexible: takes remaining space
Expanded(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text('John Doe', style: TextStyle(fontWeight: FontWeight.bold)),
Text('Online', style: TextStyle(color: Colors.green, fontSize: 12)),
],
),
),
// Fixed: action button
IconButton(icon: Icon(Icons.more_vert), onPressed: () {}),
],
)
Practical Example: Split Layouts
A two-panel layout where the left panel is narrower and the right panel is wider:
Two-Panel Split Layout
Row(
children: <Widget>[
Expanded(
flex: 1,
child: Container(
color: Colors.grey[200],
child: ListView(
children: [
ListTile(title: Text('Item 1')),
ListTile(title: Text('Item 2')),
ListTile(title: Text('Item 3')),
],
),
),
),
Expanded(
flex: 3,
child: Container(
padding: EdgeInsets.all(16),
child: Text('Content area', style: TextStyle(fontSize: 24)),
),
),
],
)
Practical Example: Sidebar + Content
A common application layout with a fixed-width sidebar and flexible content area:
Sidebar + Content Layout
Row(
children: <Widget>[
// Fixed sidebar
Container(
width: 250,
color: Colors.blueGrey[900],
child: Column(
children: [
SizedBox(height: 40),
Text('Menu', style: TextStyle(color: Colors.white, fontSize: 20)),
ListTile(
leading: Icon(Icons.dashboard, color: Colors.white),
title: Text('Dashboard', style: TextStyle(color: Colors.white)),
),
ListTile(
leading: Icon(Icons.settings, color: Colors.white),
title: Text('Settings', style: TextStyle(color: Colors.white)),
),
],
),
),
// Flexible content
Expanded(
child: Container(
padding: EdgeInsets.all(24),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text('Dashboard', style: TextStyle(fontSize: 28, fontWeight: FontWeight.bold)),
SizedBox(height: 16),
Text('Welcome back! Here is your overview.'),
],
),
),
),
],
)
Practical Example: Proportional Columns
A statistics row where each stat card takes equal space:
Equal-Width Stat Cards
Row(
children: <Widget>[
Expanded(
child: Card(
child: Padding(
padding: EdgeInsets.all(16),
child: Column(
children: [
Icon(Icons.people, size: 32, color: Colors.blue),
SizedBox(height: 8),
Text('1,234', style: TextStyle(fontSize: 24, fontWeight: FontWeight.bold)),
Text('Users', style: TextStyle(color: Colors.grey)),
],
),
),
),
),
Expanded(
child: Card(
child: Padding(
padding: EdgeInsets.all(16),
child: Column(
children: [
Icon(Icons.shopping_cart, size: 32, color: Colors.green),
SizedBox(height: 8),
Text('567', style: TextStyle(fontSize: 24, fontWeight: FontWeight.bold)),
Text('Orders', style: TextStyle(color: Colors.grey)),
],
),
),
),
),
Expanded(
child: Card(
child: Padding(
padding: EdgeInsets.all(16),
child: Column(
children: [
Icon(Icons.attach_money, size: 32, color: Colors.orange),
SizedBox(height: 8),
Text('\$9,876', style: TextStyle(fontSize: 24, fontWeight: FontWeight.bold)),
Text('Revenue', style: TextStyle(color: Colors.grey)),
],
),
),
),
),
],
)
SizedBox in scrollable contexts instead.Summary
- Expanded forces its child to fill all allocated space (FlexFit.tight).
- Flexible allows its child to be smaller than allocated space (FlexFit.loose).
- The
flexfactor controls proportional space distribution -- higher flex means more space. - Spacer creates empty flexible space, useful for pushing widgets apart.
- Flutter allocates space to fixed-size children first, then distributes remaining space among flex children.
- Expanded and Flexible must be direct children of Row, Column, or Flex.
- Do not use Expanded/Flexible inside scrollable widgets with unbounded main axis.