Dynamically Pinned List Headers
A Flutter Package Spotlight

Flutter provides a powerful solution to implement advanced scrolling in the form of Slivers
. As noted in the Flutter documents, a Sliver
is a portion of a scrollable area that you can define to behave in a special way. This allows us to combine multiple scrollable widgets (ListView
, GridView
, etc.) into a single scrollable widget where they scroll in unison, as seen in the following video.
It also allows us to implement advanced features like expanding/collapsing AppBar
s, as demonstrated below.
However, the Slivers
library leaves a few things to be desired. For example, did you notice the headers being pinned 22 seconds into the first video? They just stack upon one another like in the demo below.

There is no easy way to dynamically pin the headers as the list scrolls, replacing the currently pinned header with the one below. I set out to create a solution that could handle this, but then I discovered that another member of the Flutter community had already taken care of it! 😃 So instead of writing an article on how I implemented the solution, I get to highlight the amazing sliver_tools
package from Pieter Vanloon! Let’s take a look at how to use it.
This package provides a number of widgets that are helpful in extending Flutter’s advanced scrollable UI capabilities. We will be using two of them: MultiSliver
and SliverPinnedHeader
.
First, let’s create a Section
widget that extends MultiSliver
. We will accept parameters to define the header color, title, title color, and a list of items to be displayed below it. Then we will pass a SliverPinnedHeader
and SliverList
as children to our super
constructor.
class Section extends MultiSliver {
Section({
Key? key,
required String title,
Color headerColor = Colors.white,
Color titleColor = Colors.black,
required List<Widget> items,
}) : super(
key: key,
pushPinnedChildren: true,
children: [
SliverPinnedHeader(
child: ColoredBox(
color: headerColor,
child: ListTile(
textColor: titleColor,
title: Text(title),
),
),
SliverList(
delegate: SliverChildListDelegate.fixed(items),
),
],
);
}
The important thing to note here is the pushPinnedChildren
parameter. Setting this to true
provides the behavior we want. If it is false
, the headers will behave exactly as they did with Flutter’s Slivers
.
Now, let’s use our Section
widget in a CustomScrollView
.
CustomScrollView(
slivers: [
Section(
title: 'Category #1',
headerColor: Colors.blue,
items: List.generate(10, (index) => ListTile(
title: Text('Item #${index + 1}'),
)),
),
Section(
title: 'Category #2',
headerColor: Colors.red,
items: List.generate(10, (index) => ListTile(
title: Text('Item #${index + 11}'),
)),
),
...
],
),
For brevity, I have only shown the code for 2 sections, but you can create as many as you want. Let’s see how it looks.

That’s much better! As you can see, the pinned headers are replaced smoothly as we scroll through our sections. For a fun time, try replacing SliverPinnedHeader
with a collapsing SliverAppBar
and see what you can come up with!
As always, thank you for reading! If you found this article helpful, please clap and follow to keep up with my work with Flutter and more. Questions & comments are welcome as well.