Flutter ListView Unbounded Height Fix
A vertical viewport was given unbounded height error happens when a scrollable ListView is nested directly inside a Column without bounded vertical constraints. Learn why Column gives unbounded height, see the exact box constraints assertion, and explore solutions using Expanded, Flexible, or shrinkWrap.
Why this happens: The 'Vertical viewport was given unbounded height' error occurs when a scrollable ListView is placed directly inside a Column or another unconstrained vertical layout without bounded height constraints.
How to fix it: Wrap the ListView in an Expanded or Flexible widget so the Column allocates bounded height. Alternatively, wrap it in a SizedBox with a fixed height, or use shrinkWrap: true with physics: const NeverScrollableScrollPhysics() if nested inside an existing scrollable.
An e-commerce checkout page or account dashboard contains a static summary header (e.g. Total Amount Due or User Card), followed by a dynamic list of order line items or transaction records, and a fixed "Complete Purchase" bottom action button. The developer places these three elements directly as children of a vertical Column.
When the view builds, Flutter immediately throws a fatal layout assertion: "Vertical viewport was given unbounded height." Instead of rendering the checkout screen, the app crashes with a red error screen in debug mode or renders a completely broken, unscrollable blank state in release mode.
Observed Symptom & Flutter Error Assertion
Vertical viewport was given unbounded height error during layout.
βββββ‘ EXCEPTION CAUGHT BY RENDERING LIBRARY ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
The following assertion was thrown during layout:
Vertical viewport was given unbounded height.
Viewports expand to fill their parent along the main axis. In this case, a vertical viewport was given
an unlimited amount of vertical space in which to expand. This usually happens when a scroll view is
nested inside another scroll view, or in a Column widget.
The relevant error-causing widget was:
ListView
lib/main.dart:24:15
ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββDeclared Conditions
Placing a default scrollable ListView directly inside a Column without an explicit height constraint or Expanded/Flexible wrapper.
Column provides its children with unbounded vertical constraints (maxHeight = double.infinity). Meanwhile, a ListView viewport attempts to fill all available parent height. An infinite viewport inside an infinite parent cannot resolve geometry, triggering a layout assertion.
Wrap the ListView in an Expanded or Flexible widget so the Column allocates remaining bounded height, or give it a fixed SizedBox height constraint, or set shrinkWrap: true with physics: NeverScrollableScrollPhysics if nested inside another scrollable.
Architectural Deep Dive: Why This Failure Occurs in Flutter
Flutter Layout ProtocolIn Flutter's layout protocol, a Column (RenderFlex with vertical direction) measures its children by passing unbounded height constraints: BoxConstraints(minHeight: 0.0, maxHeight: double.infinity). It expects non-flex children to report a finite intrinsic height.
A ListView instantiates a RenderViewport. Viewports require a finite cross-axis and main-axis dimension to determine the scroll extent (maxScrollExtent = contentHeight - viewportHeight) and calculate which slivers fall within the visible window (cacheExtent).
When RenderViewport receives maxHeight == double.infinity, it cannot compute a valid scroll boundary or determine virtualization limits. It immediately throws `FlutterError("Vertical viewport was given unbounded height.")` during performLayout() before laying out sliver children.
Fills remaining Column vertical space and lazily scrolls independently.
Corrected: Bounded via Expanded or SizedBox β expanded
Wrapping ListView in an Expanded widget tells the Column to give it all remaining bounded vertical space, enabling smooth bounded scrolling.
- βEnclose ListView inside an Expanded widget
- βListView now receives bounded maxHeight equal to viewport height minus header
- βClean independent scroll physics without layout crash
import 'package:flutter/material.dart';
void main() => runApp(const FixedExpandedListDemo());
class FixedExpandedListDemo extends StatelessWidget {
const FixedExpandedListDemo({super.key});
@override
Widget build(BuildContext context) {
return MaterialApp(
debugShowCheckedModeBanner: false,
home: Scaffold(
appBar: AppBar(
title: const Text('Fixed: Expanded Column List'),
backgroundColor: const Color(0xFF0D9488),
),
body: Column(
children: [
const Padding(
padding: EdgeInsets.all(16),
child: Text('Full-Height List View', style: TextStyle(fontSize: 18, fontWeight: FontWeight.bold)),
),
// STRATEGY 1: Expanded allocates all remaining Column height and enables lazy scrolling!
Expanded(
child: ListView.builder(
itemCount: 6,
itemBuilder: (context, index) => ListTile(
leading: const Icon(Icons.check_circle, color: Color(0xFF0D9488)),
title: Text('Scrollable Item ${index + 1}'),
subtitle: const Text('Lazily rendered with bounded constraints'),
),
),
),
],
),
),
);
}
}
Alternative Fixes & Decision Framework
Compare Flutter architectural solutions to select the ideal strategy for your UX requirements.
Column(
children: [
const HeaderCard(),
Expanded(
child: ListView.builder(
itemCount: items.length,
itemBuilder: (context, i) => ItemTile(item: items[i]),
),
),
],
)When to choose: Choose when the header should remain pinned at the top and the list of items should fill the rest of the available viewport, scrolling independently with lazy item virtualization.
Trade-offs: Requires the parent Column itself to have bounded height (such as Scaffold body). Cannot be used if the Column is nested inside another vertical scroll view.
Column(
children: [
const HeaderCard(),
SizedBox(
height: 260,
child: ListView.builder(
scrollDirection: Axis.horizontal,
itemCount: items.length,
itemBuilder: (context, i) => CardTile(item: items[i]),
),
),
],
)When to choose: Choose when building horizontal carousels, embedded preview cards, or fixed-height panels that should not stretch across the entire screen.
Trade-offs: Fixed pixel heights do not automatically adapt to accessibility font scale increases or different device aspect ratios.
SingleChildScrollView(
child: Column(
children: [
const HeaderCard(),
ListView.builder(
shrinkWrap: true,
physics: const NeverScrollableScrollPhysics(),
itemCount: 5,
itemBuilder: (context, i) => SimpleTile(item: items[i]),
),
const FooterCard(),
],
),
)When to choose: Choose only when the list has very few items (under 10 items) and the entire pageβincluding headers and footersβmust scroll together as a single continuous surface.
Trade-offs: Forces Flutter to build and layout ALL list items upfront, completely disabling lazy viewport recycling. Never use for long or dynamic API feeds.
CustomScrollView(
slivers: [
const SliverToBoxAdapter(child: HeaderCard()),
SliverList.builder(
itemCount: items.length,
itemBuilder: (context, i) => ItemTile(item: items[i]),
),
const SliverToBoxAdapter(child: FooterCard()),
],
)When to choose: Choose for complex multi-section production screens where headers, feeds, grids, and footers must scroll smoothly together while preserving 60fps lazy sliver recycling.
Trade-offs: Requires converting box widgets to sliver equivalents (SliverToBoxAdapter, SliverList, SliverAppBar), which introduces moderate boilerplate.
Architectural Limitations & Edge Cases
- β Using shrinkWrap: true on large or infinite data sets causes severe UI jank because it renders every item in memory simultaneously, bypassing Flutter's element pool.
- β Placing an Expanded around a ListView inside a SingleChildScrollView creates an unresolvable flex conflict (unbounded flex height inside a scrollable flex).
- β Nested scrollables without NeverScrollableScrollPhysics cause pointer gesture conflicts where user scrolls get captured by the wrong viewport.
Common Mistakes to Avoid in Flutter
- β’Relying on shrinkWrap: true for long or infinite lists, which forces upfront evaluation and disables view recycling
- β’Nesting multiple unconstrained scroll views without ClampingScrollPhysics or CustomScrollView slivers
- β’Wrapping ListView in a SizedBox with arbitrary height that breaks on different device screens
Automated Verification Proof & Toolchain Evidence
Verification Contract: Automated test suite tests unbounded ListView in Column, captures isolated exception without failing suite, and verifies bounded strategies (Expanded, SizedBox, shrinkWrap) render 0 errors across 280px-640px heights.