Skip to main content
Tested on: Flutter 3.24.3 β€’ Dart 3.5.3
Layout & GeometryFX02Canonical Technical Guide

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.

Direct Answer & Quick Fix

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.

Realistic Production Scenario: Dashboard Feed & Order Checkout Summary
Application Context:

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.

Production Failure Impact:

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.

APIs:ListViewColumnExpandedSizedBoxshrinkWrap

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.

Root Cause Analysis

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.

Correction Rule

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 Protocol
1. Constraint Propagation

In 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.

2. RenderObject Sizing

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).

3. Why the Default Fails

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.

Evidence Mode: Live Flutter Engine
Width:
Available Height:380px
Text Scale:1.00x
Correction Strategy:

Fills remaining Column vertical space and lazily scrolls independently.

Real Flutter Web CanvasKit Engine390px β€’ 380px β€’ light

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.

Open in Studio
Key Architectural Modifications:
  • βœ“Enclose ListView inside an Expanded widget
  • βœ“ListView now receives bounded maxHeight equal to viewport height minus header
  • βœ“Clean independent scroll physics without layout crash
standalone_fx02_fixed_expanded.dartFlutter 3.24.3 β€’ Zero External Dependencies β€’ Runnable
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.

4 Strategies Audited
Expanded (Recommended for Independent Scroll)Flex Allocation (Fills Remaining Screen Height)
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.

SizedBox with Explicit HeightExplicit Pixel Constraint Boundary
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.

shrinkWrap with NeverScrollableScrollPhysicsParent Unified Scroll (Small Lists Only)
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 with SliversUnified Sliver Virtualization Architecture
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

βœ“ All 4 Tests Passing
FLUTTER ENGINEFlutter 3.24.3 (stable)
DART SDKDart 3.5.3
ENGINE REVISION36335019a8
CLI TEST COMMANDflutter test test/fixes/fx02_unbounded_test.dart

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.