CustomScrollView and Slivers: Building Collapsible, Layered Scroll UIs in Flutter
A practical guide to Flutter slivers: CustomScrollView, SliverAppBar collapsing headers, SliverList and SliverGrid, SliverToBoxAdapter, SliverPersistentHeader, NestedScrollView, and the performance wins of lazy sliver layout.
Published on • September 25, 2026
AI Assistant

A ListView gives you one scrollable list. Real screens are rarely one scrollable list. They are a photo header that collapses as you scroll, a grid of categories, a pinned section title, then an infinite feed — all in a single scroll gesture, with no jank. You cannot build that by nesting a ListView inside a ListView, and gluing widgets over a scroll view with animated offsets is a maintenance nightmare.
Slivers are Flutter’s answer. A sliver is a portion of a scrollable area with its own layout behavior. Instead of laying out whole boxes, a sliver receives scroll offsets and can size, position, and paint itself based on where the user is. Compose them in a CustomScrollView and collapsing headers, sticky sections, and mixed list/grid layouts become declarative — the scroll pipeline does the work.
CustomScrollView: The Sliver Container
CustomScrollView takes a list of slivers and stitches them into one scroll view:
CustomScrollView(
slivers: [
SliverAppBar(...),
SliverList(...),
SliverGrid(...),
],
)
The relationship runs deeper than syntax: a ListView is internally a CustomScrollView containing exactly one list sliver. CustomScrollView is not a different rendering system — it is the same system with the single-sliver restriction lifted, which is why everything composes correctly in one scroll coordinate space.
SliverAppBar: Collapsing Headers
The most-used sliver gets its own treatment because its flags are frequently confused:
SliverAppBar(
expandedHeight: 220,
pinned: true,
floating: false,
snap: false,
flexibleSpace: FlexibleSpaceBar(
title: const Text('Fancy Scrolling'),
background: Image.asset('header.jpg', fit: BoxFit.cover),
),
)
pinned: true— the bar shrinks but never leaves the screen. The classic “collapsing toolbar” pattern: expanded photo header at rest, compact toolbar after scrolling.floating: true— the bar reappears immediately on any upward scroll, without waiting for the list to reach the top. Good for search-driven feeds.snap: true— requiresfloating; any upward scroll expands the bar fully in one animated snap, rather than tracking the finger.expandedHeight+flexibleSpace— the space that collapses.FlexibleSpaceBarhandles the standard parallax background and title-scaling behavior.
SliverAppBar is itself built on SliverPersistentHeader, which you can use directly for custom collapsing headers with a delegate controlling min/max extent and shrink/overlay behavior.
Lists and Grids as Slivers
SliverList and SliverGrid are the lazy workhorses. Use the .builder delegate constructors to build children on demand:
SliverGrid(
gridDelegate: const SliverGridDelegateWithMaxCrossAxisExtent(
maxCrossAxisExtent: 200.0,
mainAxisSpacing: 8.0,
crossAxisSpacing: 8.0,
childAspectRatio: 1.0,
),
delegate: SliverChildBuilderDelegate(
(context, index) => ItemTile(index),
childCount: 1000,
),
)
For long uniform lists, SliverFixedExtentList takes an itemExtent and unlocks a further optimization: because every child’s extent is known ahead of time, the viewport can jump directly to a scroll offset without laying out intermediate children — fast scrolling through a hundred thousand rows.
The Utility Slivers
SliverToBoxAdapter— wraps an ordinary box widget (a header, a banner, a divider) so it can live inside aCustomScrollView. The bridge between box world and sliver world.SliverPadding— appliesEdgeInsetsas a sliver. Wrapping the whole scroll view in aPaddinginstead changes the viewport geometry and breaks the slivers inside — padding must be applied in sliver space.SliverFillRemaining— occupies the rest of the viewport regardless of content size. Natural fit for empty states and full-screen “load more” footers.SliverOpacity,SliverIgnorePointer,SliverLayoutBuilder,SliverAnimatedOpacity,SliverFadeTransition— sliver versions of their box counterparts, so you can animate or gate a whole scroll section without reparenting it into a box.
A Complete Screen
Putting it together — collapsing header, section label, grid, then a long fixed-extent list, all in one scroll:
CustomScrollView(
slivers: [
SliverAppBar(
expandedHeight: 220,
pinned: true,
flexibleSpace: FlexibleSpaceBar(
title: const Text('Fancy Scrolling'),
background: Image.asset('header.jpg', fit: BoxFit.cover),
),
),
const SliverToBoxAdapter(child: SectionHeader('Latest')),
SliverPadding(
padding: const EdgeInsets.all(8),
sliver: SliverGrid(
gridDelegate: const SliverGridDelegateWithFixedCrossAxisExtent(
crossAxisCount: 2,
mainAxisSpacing: 8,
crossAxisSpacing: 8,
),
delegate: SliverChildBuilderDelegate(
(_, i) => Card(child: Center(child: Text('Item $i'))),
childCount: 20,
),
),
),
SliverFixedExtentList(
itemExtent: 60,
delegate: SliverChildBuilderDelegate(
(_, i) => ListTile(title: Text('Row $i')),
childCount: 100,
),
),
],
)
No Column with Expanded, no nested scroll controllers, no manual header offset math — one scroll position, every effect native to the layout system.
NestedScrollView for Header + Tab Body
The pattern “collapsing header on top, TabBarView of lists below” has a dedicated tool: NestedScrollView coordinates an outer scrollable (typically a SliverAppBar) with inner body scrollables so the header collapses first, then the inner list scrolls — while preserving a single overall scroll position. The inner lists must use the controllers NestedScrollView provides, and SliverOverlapAbsorberHandle keeps pinned tab bars from sliding under the collapsing header. The Flutter docs cover this pattern in detail; the key mistake to avoid is giving the inner lists their own ScrollController.
CustomScrollView vs ListView: When to Use Which
Use a plain ListView/GridView for a simple, homogeneous list — less code, identical lazy behavior. Reach for CustomScrollView when you need:
- A collapsing, floating, or pinned app bar over content
- Multiple sections of different kinds (box + grid + list) in one scroll
- Sticky section headers or scroll-position-dependent layout
- Effects built in the scroll pipeline instead of
MediaQuery-driven rebuild hacks or externalAnimationControllers
The performance argument is real as well: lazy building with .builder delegates constructs only visible children, and fixed-extent slivers enable offset-based skipping. For very long or jump-heavy lists, slivers are not just more capable — they are faster.
Tips from the Trenches
shrinkWrap: trueon a ListView inside a CustomScrollView is a smell. You already have a sliver viewport; convert the list toSliverListinstead of nesting a second scrollable.- Always prefer
.builderdelegates in slivers — the non-builder constructors build every child eagerly. - Parallax and floating app bars have cookbook recipes (“Place a floating app bar above a list”, “Create a scrolling parallax effect”) — steal from those before inventing geometry.
- For deep understanding, the community references are “Slivers, Demystified” and the “Slivers explained” Boring Show episode with Ian Hickson, plus the API docs for
CustomScrollView,SliverAppBar, andSliverGrid.
Summary
Slivers are the difference between scrolling as a container and scrolling as a layout system. Once you think in slivers, the screens that felt impossible — photo headers dissolving into pinned toolbars, grids flowing into infinite feeds, sections that fade as they leave — collapse into a declarative list of sliver widgets with the framework handling all the offset math. Start with SliverAppBar over a SliverList, and you will never go back to nested scroll views.