Slivers
Building fancy scrolling effects — from collapsing headers to elastic scrolling — using Flutter's sliver system.
A sliver is a portion of a scrollable area that you can define to behave in a special way. Slivers are the building blocks of custom scrolling in Flutter — they give you fine-grained control over how your scrollable content behaves.
💡 Key Concept
Think of slivers as
"scrollable pieces"
that can be combined to create
complex scrolling effects. Unlike
ListView
or
GridView
which
are self-contained, slivers are
components
that can be arranged in a
CustomScrollView
to create custom scroll experiences.
CustomScrollView
is the container that holds your slivers. It's the
root
of any sliver-based scrolling implementation.
SliverAppBar SliverList SliverGrid SliverPersistentHeader
]
return
Scaffold(
body: CustomScrollView(
slivers: [
// 1. App Bar (collapsible)
SliverAppBar(
title: Text(
'My Page'
),
floating:
true
,
expandedHeight:
200
,
flexibleSpace: FlexibleSpaceBar(
title: Text(
'Collapsing Header'
),
background: Image.network(
'https://example.com/header.jpg'
),
),
),
// 2. A regular widget (non-scrollable)
SliverToBoxAdapter(
child: Padding(
padding: EdgeInsets.
all
(
16
),
child: Text(
'Section Header'
),
),
),
// 3. Scrollable list of items
SliverList(
delegate: SliverChildBuilderDelegate(
(context, index) => ListTile(
title: Text(
'Item $index'
),
),
childCount:
100
,
),
),
// 4. Another regular widget
SliverToBoxAdapter(
child: Padding(
padding: EdgeInsets.
all
(
16
),
child: ElevatedButton(
onPressed: () {},
child: Text(
'Load More'
),
),
),
),
],
),
);
SliverAppBar
is one of the most commonly used slivers. It creates a
collapsible app bar
that can expand and contract as the user scrolls.
SliverAppBar(
// Basic configuration
title: Text(
'Profile'
),
centerTitle:
true
,
// Expand/collapse behavior
expandedHeight:
300
,
collapsedHeight:
80
,
floating:
true
,
pinned:
true
,
snap:
false
,
// Background
flexibleSpace: FlexibleSpaceBar(
title: Text(
'John Doe'
),
centerTitle:
true
,
background: Container(
decoration: BoxDecoration(
gradient: LinearGradient(
begin: Alignment.topLeft,
end: Alignment.bottomRight,
colors: [Colors.blue, Colors.purple],
),
),
child: Align(
alignment: Alignment.bottomCenter,
child: CircleAvatar(
radius:
50
,
backgroundImage: NetworkImage(
'https://i.pravatar.cc/150?img=1'
),
),
),
),
),
// Actions
actions: [
IconButton(
icon: Icon(Icons.settings),
onPressed: () {},
),
],
// Bottom widget
bottom: PreferredSize(
preferredSize: Size.fromHeight(
50
),
child: Container(
color: Colors.white,
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
children: [
Text(
'Posts: 42'
),
Text(
'Followers: 1.2K'
),
Text(
'Following: 89'
),
],
),
),
),
)
💡 SliverAppBar Properties
- floating – App bar reappears when scrolling up
- pinned – App bar stays at the top when collapsed
- snap – App bar snaps open when partially scrolled
- expandedHeight – Height when fully expanded
- collapsedHeight – Height when fully collapsed
- flexibleSpace – Content that scales with the app bar
SliverList
and
SliverGrid
are the scrollable content slivers
that display collections of items.
SliverList(
delegate: SliverChildBuilderDelegate(
(context, index) {
return
Container(
height:
60
,
margin: EdgeInsets.
symmetric
(vertical:
4
),
color: Colors.primaries[index % Colors.primaries.
length
],
child: Center(
child: Text(
'Item $index'
,
style: TextStyle(color: Colors.white),
),
),
);
},
childCount:
50
,
),
)
SliverGrid(
gridDelegate: SliverGridDelegateWithMaxCrossAxisExtent(
maxCrossAxisExtent:
200
,
mainAxisSpacing:
10
,
crossAxisSpacing:
10
,
childAspectRatio:
1.0
,
),
delegate: SliverChildBuilderDelegate(
(context, index) {
return
Container(
decoration: BoxDecoration(
color: Colors.primaries[index % Colors.primaries.
length
],
borderRadius: BorderRadius.
circular
(
8
),
),
child: Center(
child: Text(
'$index'
,
style: TextStyle(
color: Colors.white,
fontSize:
24
,
fontWeight: FontWeight.bold,
),
),
),
);
},
childCount:
30
,
),
)
💡 Delegate Options
- SliverChildBuilderDelegate – Build items lazily (recommended)
- SliverChildListDelegate – Provide a pre-built list of children
- SliverChildBuilderDelegate – Better for large lists (lazy loading)
SliverPersistentHeader
creates a header that
sticks
to the top
of the scroll view as you scroll past it. This is perfect for section headers, sticky tabs,
or any content that should remain visible.
class
StickyHeaderDelegate
extends
SliverPersistentHeaderDelegate {
final
String title;
StickyHeaderDelegate
(
this
.title);
@override
Widget
build
(
BuildContext context,
double
shrinkOffset,
bool
overlapsContent,
) {
return
Container(
color: Colors.blue,
padding: EdgeInsets.
symmetric
(vertical:
12
, horizontal:
16
),
child: Row(
children: [
Icon(Icons.label, color: Colors.white),
SizedBox(width:
8
),
Text(
title,
style: TextStyle(
color: Colors.white,
fontSize:
18
,
fontWeight: FontWeight.bold,
),
),
],
),
);
}
@override
double
get maxExtent =>
60
;
@override
double
get minExtent =>
60
;
@override
bool
shouldRebuild(StickyHeaderDelegate oldDelegate) {
return
title != oldDelegate.title;
}
}
// Usage in CustomScrollView
CustomScrollView(
slivers: [
SliverToBoxAdapter(
child: Text(
'Section 1'
),
),
SliverPersistentHeader(
delegate: StickyHeaderDelegate(
'Category A'
),
),
SliverList(
delegate: SliverChildBuilderDelegate(
(context, index) => ListTile(title: Text(
'Item $index'
)),
childCount:
10
,
),
),
SliverPersistentHeader(
delegate: StickyHeaderDelegate(
'Category B'
),
),
SliverList(
delegate: SliverChildBuilderDelegate(
(context, index) => ListTile(title: Text(
'Item $index'
)),
childCount:
10
,
),
),
],
)
✅ When to Use SliverPersistentHeader
- Sticky section headers – Like a contacts list (A, B, C...)
- Sticky tabs – Tab bar that stays at the top
- Sticky search bars – Search field that scrolls with content
- Sticky filters – Filter options that remain accessible
The real power of slivers comes from combining different types to create complex, polished scrolling experiences.
class
ProfilePage
extends
StatelessWidget {
@override
Widget
build
(BuildContext context) {
return
Scaffold(
body: CustomScrollView(
slivers: [
// 1. Collapsible App Bar
SliverAppBar(
expandedHeight:
250
,
floating:
true
,
pinned:
true
,
flexibleSpace: FlexibleSpaceBar(
title: Text(
'Profile'
),
background: Image.network(
'https://example.com/cover.jpg'
,
fit: BoxFit.cover,
),
),
),
// 2. Profile Info (static)
SliverToBoxAdapter(
child: Padding(
padding: EdgeInsets.
all
(
16
),
child: Column(
children: [
Text(
'John Doe'
,
style: TextStyle(fontSize:
24
, fontWeight: FontWeight.bold),
),
Text(
'Flutter Developer'
,
style: TextStyle(fontSize:
16
, color: Colors.grey[
600
]),
),
SizedBox(height:
16
),
Row(
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
children: [
_buildStat(
'Posts'
,
'42'
),
_buildStat(
'Followers'
,
'1.2K'
),
_buildStat(
'Following'
,
'89'
),
],
),
SizedBox(height:
16
),
ElevatedButton(
onPressed: () {},
child: Text(
'Follow'
),
),
],
),
),
),
// 3. Sticky Tab Bar
SliverPersistentHeader(
delegate: _TabHeaderDelegate(),
pinned:
true
,
),
// 4. Content (Grid view)
SliverGrid(
gridDelegate: SliverGridDelegateWithMaxCrossAxisExtent(
maxCrossAxisExtent:
150
,
mainAxisSpacing:
2
,
crossAxisSpacing:
2
,
),
delegate: SliverChildBuilderDelegate(
(context, index) {
return
Container(
color: Colors.primaries[index % Colors.primaries.
length
],
child: Center(
child: Text(
'${index + 1}'
,
style: TextStyle(color: Colors.white, fontSize:
20
),
),
),
);
},
childCount:
50
,
),
),
],
),
);
}
Widget
_buildStat
(String label, String value) {
return
Column(
children: [
Text(
value,
style: TextStyle(fontSize:
20
, fontWeight: FontWeight.bold),
),
Text(label, style: TextStyle(color: Colors.grey[
600
])),
],
);
}
}
class
_TabHeaderDelegate
extends
SliverPersistentHeaderDelegate {
@override
Widget
build
(BuildContext context,
double
shrinkOffset,
bool
overlapsContent) {
return
Container(
color: Colors.white,
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
children: [
_buildTab(
'Posts'
,
true
),
_buildTab(
'Photos'
,
false
),
_buildTab(
'Videos'
,
false
),
],
),
);
}
Widget
_buildTab
(String text,
bool
isActive) {
return
Padding(
padding: EdgeInsets.
symmetric
(vertical:
12
),
child: Column(
children: [
Text(
text,
style: TextStyle(
fontSize:
16
,
fontWeight: isActive ? FontWeight.bold : FontWeight.normal,
color: isActive ? Colors.blue : Colors.grey,
),
),
if
(isActive)
Container(
height:
2
,
width:
30
,
color: Colors.blue,
),
],
),
);
}
@override
double
get maxExtent =>
50
;
@override
double
get minExtent =>
50
;
@override
bool
shouldRebuild(_TabHeaderDelegate oldDelegate) =>
false
;
}
Follow these steps to implement slivers in your Flutter app:
ListView
with
CustomScrollView
SliverToBoxAdapter
for non-scrollable widgets
SliverList
or
SliverGrid
SliverPersistentHeader
for section headers
SliverPadding
to add space between slivers
You can't put regular widgets directly in
CustomScrollView.slivers
.
Use
SliverToBoxAdapter(child: YourWidget())
to add regular widgets to a sliver list.
Not implementing
shouldRebuild
in custom delegates can cause unnecessary rebuilds.
Always implement
shouldRebuild
in your custom delegates to control when they rebuild.
SliverChildListDelegate
builds all children at once, which is inefficient for large lists.
Use
SliverChildBuilderDelegate
for lazy loading of large lists.
Hardcoding heights in slivers can cause layout issues on different screen sizes.
Use
MediaQuery
,
LayoutBuilder
, or dynamic calculations for sliver heights.
📚 Learning Resources
- Flutter Slivers Docs – Official documentation
- Slivers, Demystified – Medium article
- SliverAppBar Widget of the Week – 1-minute video
- SliverList and SliverGrid Widget of the Week – 1-minute video
🔗 Related Lessons
- Animations – Combine slivers with animations
- Custom Widgets – Building reusable components
- Module 6 Overview – All advanced topics
🎯 Key Takeaway
Slivers
are the building blocks of custom scrolling in Flutter. Using
CustomScrollView
with different sliver types —
SliverAppBar
,
SliverList
,
SliverGrid
, and
SliverPersistentHeader
—
you can create
fancy scrolling effects
like collapsing headers, sticky
sections, and elastic scrolling. Always use
SliverChildBuilderDelegate
for
large lists and implement
shouldRebuild
in custom delegates for performance.