Flutter: The 'Vertical viewport was given unbounded height' Error
1. "The Screen Went White."
The #2 scariest moment for a Flutter dev (after build errors) is launching the app to see a White Screen of Death, or a red screen yelling "Vertical viewport was given unbounded height."
The code was simple:
Column(
children: [
Text("My Todo List"), // Header
ListView.builder( // List
itemCount: 100,
itemBuilder: (context, index) => Text("Task $index"),
),
],
)
"Wait, putting a list under a title is hard?" Yes, in Flutter's layout universe, this is a deep philosophical paradox.
2. The Principle: Clash of Infinities
The core of this error is a fight over "Who decides the Height?"
- Column's Instinct:
Columnarranges children vertically. It asks children: "How much height do you need? I'll give it to you." (Respects children's size). - ListView's Instinct:
ListViewis scrollable. This implies content is longer than the screen. Theoretically,ListViewhas Infinite Height. - The Clash:
Column: "Hey ListView, what's your height? I need to draw you."ListView: "I am Infinite."Column: "..." (Cannot draw infinity).- Result:
Unbounded heighterror. Flutter cannot render a widget with infinite height inside a layout that tries to wrap content.
3. Solution 1: Expanded (The Easy Fix)
Tell ListView: "Stop claiming infinity. Just take whatever space Column has left."
Column(
children: [
Text("My Todo List"), // Fixed size (e.g., 50px)
Expanded( // Takes ALL remaining space
child: ListView.builder(
itemCount: 100,
itemBuilder: ...,
),
),
],
)
Expanded calculates the remaining height of the screen (Total - 50px) and forces a Bounded Height onto ListView.
ListView realizes, "Oh, I only exist within this box," and creates a scroll view normally.
In 90% of cases, this is the answer.
4. Solution 2: SizedBox (Fixed Height)
If you want a specific height, wrap in SizedBox or Container.
Column(
children: [
Text("Header"),
SizedBox(
height: 200, // Fixed to 200 logical pixels
child: ListView(...),
),
Text("Footer"),
],
)
But this is bad for Responsive Design. It might look tiny on tablets or overflow on small phones.
5. Solution 3: shrinkWrap (The Performance Trap)
The most misused property.
ListView(
shrinkWrap: true, // ⚠️ Warning!
physics: NeverScrollableScrollPhysics(),
...
)
shrinkWrap: true tells ListView: "Don't be infinite. Shrink yourself to fit your children."
Sounds good, but it has a fatal flaw:
Lazy Loading stops working.
If you have 100 items, it renders all 100. If 1,000, it renders 1,000 at once to calculate height.
As the list grows, you will experience severe lag (Jank). Avoid if possible.
6. Solution 4: CustomScrollView + Slivers (The Pro Way)
What if the requirements are complex? "The header must scroll WITH the list." "The header should shrink/fade as I scroll (SliverAppBar)."
Here, you must abandon the Column + ListView structure.
Instead, use CustomScrollView and put everything as Slivers.
CustomScrollView(
slivers: [
// 1. Adapt normal widgets (Text) to Sliver World
SliverToBoxAdapter(
child: Padding(
padding: EdgeInsets.all(16.0),
child: Text("My Todo List", style: TextStyle(fontSize: 24)),
),
),
// 2. Sliver-optimized List
SliverList(
delegate: SliverChildBuilderDelegate(
(context, index) => ListTile(title: Text("Task $index")),
childCount: 100,
),
),
],
)
Why Slivers?
Sliver means a "slice" of the scrollable area. It's a high-performance system that only renders the viewport slice.
While Column + ListView separates scroll areas (fixed header, scrolling list), CustomScrollView treats everything as one unified scroll. Much better UX.
7. Deep Dive: LimitedBox, Flexible, and Space
Is there an alternative to Expanded?
Yes, LimitedBox.
LimitedBox: "You can be infinite usually, but if your parent gives you Unbounded space (like Column), then force yourself to be max 300px." It's useful for widgets that should expand in a ScrollView but shrink in a Column.
The Scroll-in-Scroll Nightmare:
Never put a ListView inside a SingleChildScrollView with shrinkWrap: true.
It's creating an infinite canvas inside an infinite canvas. The Flutter engine weeps.
Always use CustomScrollView for mixing different scrollable contents.
8. Deep Dive: NestedScrollView (The Final Boss)
What about a TabBar where each tab has a list (e.g., Instagram Profile)? The whole page scrolls, but the inner tab list also scrolls.
Enter NestedScrollView.
NestedScrollView(
headerSliverBuilder: (context, _) => [
SliverAppBar(title: Text("Profile"), pinned: true),
],
body: TabBarView(
children: [
ListView(...), // Tab 1
ListView(...), // Tab 2
],
),
)
This is Level 99 usage. But the principle is the same: Clarify "Who controls the scroll?"
9. Pro Tip: Preserving Scroll State (AutomaticKeepAliveClientMixin)
If you have tabs with lists, swapping tabs destroys the list state (scroll position resets to top). Users hate this.
The Fix: Use AutomaticKeepAliveClientMixin.
class MyListPage extends StatefulWidget { ... }
class _MyListPageState extends State<MyListPage> with AutomaticKeepAliveClientMixin {
@override
bool get wantKeepAlive => true; // Magic line
@override
Widget build(BuildContext context) {
super.build(context); // Must call this
return ListView(...);
}
}
This keeps the widget "alive" in memory even when it's off-screen.
Essential for TabBarView or PageView layouts.
10. Deep Dive Glossary
- Viewport: The viewing window. The scroll view shows only a portion of the content through this viewport.
- Offset: How far you have scrolled. 0.0 is top.
- Extent: The size of a Sliver. Height for vertical, width for horizontal.
- Overscroll: The bouncing effect on iOS or the glow effect on Android when hitting the edge.
11. Summary: Selection Guide
-
Header fixed, only list scrolls? ->
Column+Expanded+ListView(Recommended). -
Header scrolls WITH list? -> Put Header as the first item in
ListView, OR ->CustomScrollView+SliverToBoxAdapter+SliverList(Best). -
Very few items (< 10)? ->
Column+SingleChildScrollView. No need forListView. -> OrshrinkWrap: true(Last resort). -
Tabs + Complex Scroll? ->
NestedScrollView.
The "Unbounded height" error isn't trying to annoy you. It's Flutter asking, "How do you want me to fit an infinite universe into a finite phone screen?"
Just put it in an Expanded box, and everyone is happy.