Flutter Sticky Header: SliverPersistentHeader, SliverAppBar and NestedScrollView (2026)

Build a Flutter sticky header — a fixed navigation bar that stays visible as users scroll. Code examples, scroll patterns, and use cases for lists.

Flutter Sticky Header: SliverPersistentHeader, SliverAppBar and NestedScrollView (2026) — hero image

Across the ten industries we ship Flutter in, the sticky header shows up wherever a scrolling list needs to anchor a section title, a category label, or a date row to the top of the viewport as content scrolls past beneath it. The choice between SliverPersistentHeader, SliverAppBar with the pinned flag, the community sticky_headers package, or a hand-rolled OffstageBuilder pattern looks small at design time and turns into a performance, coordination, or M3-conformance question once the app hits real users. This guide walks through the flutter sticky header patterns we actually ship, the Sliver primitives most tutorials skip, and the production-grade fixes worth knowing on every list-heavy Flutter build.

Two framing notes. Flutter ships three native sticky-header primitives in the Material library that cover about 90% of the sticky-header use cases real apps need. SliverPersistentHeader for custom in-list section headers, SliverAppBar with pinned: true for app-bar-style top docking, and SliverGrid plus SliverList combined with NestedScrollView for tabbed scrolling surfaces. Most pub.dev packages predate full Sliver support and have been overtaken by the built-in primitives on the modern Flutter versions our team has shipped against this year. Second: the sticky-header pattern is sensitive to scroll coordination — getting it right inside a TabBarView or a CustomScrollView needs SliverOverlapAbsorber and SliverOverlapInjector, which is where most production bugs live.

When the flutter sticky header is the right pattern (and when it isn't)

Use a sticky header when scrolling content has clear section breaks that benefit from a persistent visual cue, such as a contacts list grouped by first letter, an order history grouped by date, a settings screen with category labels, or a feed broken by content type. The pattern shows up across the production builds we ship most often: chat threads grouped by date, audit logs grouped by status, dashboard sections labeled by KPI category. Whenever a list crosses about 30 rows with a clear grouping structure, a sticky header improves scan time measurably. Sticky headers reduce scroll fatigue and improve scanning. The reason to skip them: when the content has no natural section structure or when the visual weight of a persistent header competes with the content rather than supports it.

NeedUseWhy
Section headers inside a scrolling list (A-Z contacts, date-grouped feed)SliverPersistentHeaderNative, no package, full sticky behavior
App-bar that stays visible while body scrollsSliverAppBar with pinned: trueBuilt-in M3 app-bar contract; right size automatically
Collapsing hero header + sticky toolbarSliverAppBar.medium or SliverAppBar.largeM3-aligned variants; flexibleSpace + bottom slot
Tabbed surface with sticky tabs over scrolling bodyNestedScrollView + SliverAppBar bottom: TabBarSliverOverlapAbsorber pattern coordinates scroll
Quick prototyping or legacy codesticky_headers packageAPI simpler than Sliver primitives; pay bundle cost
Complex per-item sticky (one per group)SliverList grouped + SliverPersistentHeaderDelegateManual but full control over per-section pin behavior
Reach for which sticky-header primitive on each layout pattern

SliverPersistentHeader: the built-in sticky primitive for custom in-list section headers

SliverPersistentHeader is the Flutter primitive purpose-built for sticky behavior inside a CustomScrollView. It takes a SliverPersistentHeaderDelegate that defines the header content, its min and max height, and whether it should stay pinned to the top as the user scrolls past. The delegate is a regular Dart class. Override build, minExtent, maxExtent, and shouldRebuild and you have a custom header. The widget exposes pinned and floating flags that work the same way they do on SliverAppBar, so the mental model carries over for teams familiar with the app-bar variant. The widget integrates with the scroll position natively, so the header transitions smoothly between expanded and collapsed states.

lib/widgets/sticky_section.dart
DART
class _SectionHeaderDelegate extends SliverPersistentHeaderDelegate {
  final String label;
  const _SectionHeaderDelegate(this.label);

  @override
  double get minExtent => 48;
  @override
  double get maxExtent => 48;

  @override
  Widget build(BuildContext context, double shrinkOffset, bool overlapsContent) {
    return Material(
      color: Theme.of(context).colorScheme.surfaceContainerLow,
      child: Padding(
        padding: const EdgeInsets.symmetric(horizontal: 16),
        child: Align(
          alignment: Alignment.centerLeft,
          child: Text(label, style: Theme.of(context).textTheme.titleSmall),
        ),
      ),
    );
  }

  @override
  bool shouldRebuild(_SectionHeaderDelegate old) => old.label != label;
}

// Inside CustomScrollView:
CustomScrollView(
  slivers: [
    SliverPersistentHeader(pinned: true, delegate: _SectionHeaderDelegate('A')),
    SliverList(delegate: SliverChildListDelegate(aContacts)),
    SliverPersistentHeader(pinned: true, delegate: _SectionHeaderDelegate('B')),
    SliverList(delegate: SliverChildListDelegate(bContacts)),
  ],
);

SliverAppBar with pinned: true: the app-bar sticky pattern for long-scroll screens

When the sticky header is conceptually an app-bar that needs to remain visible while the body scrolls past, SliverAppBar with pinned: true is the right primitive and the easiest sticky pattern to ship. The framework handles the M3 elevation, the surface tint, the back-arrow Hero animation, and the title styling automatically. We use this pattern on screens where the user scrolls a long feed and needs persistent access to the back arrow or a primary action.

lib/screens/contacts_screen.dart
DART
Scaffold(
  body: CustomScrollView(
    slivers: [
      SliverAppBar(
        pinned: true,                          // stays visible while body scrolls
        title: const Text('Contacts'),
        actions: [IconButton(icon: const Icon(Icons.search), onPressed: () {})],
      ),
      SliverList(
        delegate: SliverChildBuilderDelegate(
          (ctx, i) => ListTile(title: Text(contacts[i].name)),
          childCount: contacts.length,
        ),
      ),
    ],
  ),
);

NestedScrollView: sticky tabs over a scrolling body with overlap coordination

The hardest sticky-header pattern to get right is a TabBar that stays pinned to the top while the body of each tab scrolls independently. This is the layout most production Pinterest-style and social-feed-style apps reach for, and it is the layout where most community packages give up. NestedScrollView handles this layout with headerSliverBuilder (the slot where you put your SliverAppBar with bottom: TabBar) and body (the slot where you put the TabBarView and the individual tab scroll surfaces). Two things must be wired right for the pattern to behave correctly under aggressive flicks and pull-to-refresh interactions. Every child of the TabBarView needs to wrap its scroll view in a CustomScrollView with SliverOverlapInjector at the top of its slivers list. The SliverAppBar needs SliverOverlapAbsorber as its parent inside the headerSliverBuilder list. Get either of these wrong and the inner scroll positions fight with the outer NestedScrollView, which produces the jumpy scroll behavior most developers blame on Flutter performance.

lib/screens/tabbed_screen.dart
DART
DefaultTabController(
  length: 3,
  child: Scaffold(
    body: NestedScrollView(
      floatHeaderSlivers: true,
      headerSliverBuilder: (ctx, innerBoxScrolled) => [
        SliverOverlapAbsorber(
          handle: NestedScrollView.sliverOverlapAbsorberHandleFor(ctx),
          sliver: SliverAppBar(
            pinned: true,
            title: const Text('Activity'),
            bottom: const TabBar(tabs: [
              Tab(text: 'Posts'),
              Tab(text: 'Replies'),
              Tab(text: 'Likes'),
            ]),
          ),
        ),
      ],
      body: const TabBarView(
        children: [_PostsTab(), _RepliesTab(), _LikesTab()],
      ),
    ),
  ),
);

class _PostsTab extends StatelessWidget {
  const _PostsTab();
  @override
  Widget build(BuildContext context) => CustomScrollView(
    slivers: [
      SliverOverlapInjector(
        handle: NestedScrollView.sliverOverlapAbsorberHandleFor(context),
      ),
      SliverList(delegate: SliverChildBuilderDelegate((c, i) => ListTile(title: Text('Post $i')), childCount: 50)),
    ],
  );
}

Community sticky_headers package: when is it still worth it in 2026?

The sticky_headers package on pub.dev predates the modern Sliver scroll model and ships a simpler API for list-style sticky sections. The migration to SliverPersistentHeader is typically under 50 lines per screen and saves the package dependency, but the package API is friendlier for teams not already comfortable with the Sliver model. For new builds where the team can invest in learning Slivers, the built-in is the better choice. For legacy screens already on the package, do not migrate until you are touching the surrounding code anyway.

Two more community packages worth knowing about for legacy codebases that already have them installed: flutter_sticky_header (similar API to sticky_headers with extra customization for floating headers) and sticky_grouped_list (handles grouped lists with sticky group labels in one widget). Both packages work but cover use cases that SliverPersistentHeader plus a simple grouping helper can model natively on modern Flutter versions. We do not adopt either on new builds in 2026 because the maintenance burden across major Flutter upgrades has been higher than building the equivalent natively.

Performance: where sticky headers most often make Flutter apps jank

Accessibility: what sticky headers need that the framework default does not ship

GapSymptomFix
Header not announced as a headingScreen reader reads header as inline textWrap header content in Semantics(header: true)
Sticky header overlaps content on text-scale 1.5+Header expands; content scrolls beneathCompute minExtent dynamically from MediaQuery.textScaler
Pinned NestedScrollView tabs lose keyboard focusTab key cycles past tabs to invisible contentUse FocusableActionDetector or scope focus to visible TabView
Section header transitions confuse screen readersReader announces 'A' then 'B' as user scrolls pastUse Semantics.liveRegion: false to silence transition announces
Accessibility gaps in default flutter sticky header usage and how to fix them

The five flutter sticky header bugs we see in code review every month

BugSymptomFix
NestedScrollView body without SliverOverlapInjectorInner list scrolls under the AppBar instead of beneath itWrap each child in CustomScrollView with SliverOverlapInjector at top
SliverPersistentHeader minExtent != maxExtent without animationHeader pops at scroll boundary instead of animatingMake extents equal for non-animated; or animate via overlapsContent
Missing SliverOverlapAbsorber on tabbed surfaceInner scroll position fights with outer when user flicksAlways wrap SliverAppBar in SliverOverlapAbsorber inside headerSliverBuilder
Sticky_headers package conflicting with Sliver semanticsHeader position drifts on aggressive flickPick one paradigm; mixing Sliver and non-Sliver scroll causes coordination bugs
floatHeaderSlivers: true with snap: falseHeader reappears on upward scroll without smoothingPair floatHeaderSlivers with snap: true on the SliverAppBar
Recurring flutter sticky header bugs and the one-line fixes we apply in code review

For the deeper SliverAppBar variant guide plus pinned/floating/snap combinations that pair with sticky headers on real-world Flutter app builds, see our guide to flutter AppBar widgets. For how scroll patterns fit into a production Flutter app (state plus performance plus CI/CD coverage) our Flutter mobile app development field guide covers the practices we apply on every build.

Common questions about the flutter sticky header pattern from our review checklist

What is the flutter sticky header pattern in production Flutter apps?

A sticky header anchors a section title or app bar to the top of the viewport as the user scrolls content beneath it. Flutter ships three native primitives that cover the use case: SliverPersistentHeader for custom in-list section headers, SliverAppBar with pinned: true for app-bar-style top docking, and NestedScrollView with SliverOverlapAbsorber for tabbed scrolling surfaces.

What is SliverPersistentHeader in Flutter and how is it different from SliverAppBar?

SliverPersistentHeader is the framework primitive built for sticky behavior inside a CustomScrollView. It takes a SliverPersistentHeaderDelegate that defines content, min and max extent, and whether it pins to the top. The widget integrates with scroll position natively, so the header transitions smoothly between expanded and collapsed states without any package dependency.

Should I use the sticky_headers community package or the built-in Sliver primitives?

For new builds, prefer built-in Slivers (SliverPersistentHeader, SliverAppBar). The migration from sticky_headers to native is typically under 50 lines per screen and saves the package dependency. For legacy code already on the package, do not migrate until you are touching the surrounding code anyway.

How do I make a tab bar sticky in Flutter while the body of each tab scrolls?

Use NestedScrollView. Put a SliverAppBar with bottom: TabBar inside headerSliverBuilder (wrapped in SliverOverlapAbsorber). Put a TabBarView in body. Every child of the TabBarView must wrap its scroll view in a CustomScrollView with SliverOverlapInjector at the top. This is the canonical pattern; community packages skip the overlap coordination and break on aggressive scroll.

What is SliverOverlapAbsorber used for in Flutter scroll patterns?

SliverOverlapAbsorber and SliverOverlapInjector coordinate scroll between an outer NestedScrollView and inner scroll views in tabs. The Absorber wraps the SliverAppBar in headerSliverBuilder. The Injector goes at the top of each inner CustomScrollView. Without them, inner lists scroll under the AppBar instead of beneath it, which looks broken.

How do I improve sticky header performance on long lists in Flutter?

Three rules. Cache the SliverPersistentHeaderDelegate instance so shouldRebuild only fires when content actually changes. Keep header content static — no StreamBuilder inside the header. Wrap heavy headers (backdrop blur, images) in RepaintBoundary. These three changes take a 500-row contact list with a backdrop-blur sticky header from 38fps to 60fps on a mid-range Android device.

How do I make Flutter sticky headers accessible to screen reader users?

Wrap the header content in Semantics(header: true) so screen readers announce it as a heading. Compute minExtent dynamically from MediaQuery.textScaler so larger system fonts do not cause header overlap with content. For NestedScrollView tabs, scope focus to the visible TabBarView child so keyboard nav does not cycle past the visible tab into hidden content.

What is the difference between pinned and floating plus snap flags on SliverAppBar?

Three flags that combine in different ways for different sticky-header behaviors. pinned keeps the toolbar visible while the expanded region scrolls out. floating brings the bar back on any upward scroll. snap (with floating) snaps the bar fully in or out. Combined pinned + floating means the bar stays visible and the expanded region returns on upward scroll. For a pure sticky-header use case where the bar must always stay visible, pinned: true alone is the right and simplest choice.

MORE IN /FLUTTER APP DEVELOPMENT COMPANY

Continue reading.

Stacked horizontal app bar primitives collapsing over a content surface, editorial illustration
#flutter#appbar

Top 10 Best Flutter AppBar Widgets: SliverAppBar, M3 Variants and Migration (2026)

Top 10 Flutter AppBar widgets to build sticky top toolbars — title styling, action buttons, theming, and GetWidget's GFAppBar with code examples.

Navin Sharma Navin Sharma
8m
flutter tabbar widget hero image
#flutter#tabbar

Flutter TabBar Widget in 2026: TabBar, TabBarView, TabController, and the Material 3 Primary vs Secondary Distinction

Build a Flutter TabBar widget to navigate between pages in a single view. Customize indicators, handle controllers, and pair with GetWidget's GFTabs.

Navin Sharma Navin Sharma
7m
Flutter Mobile App Development: A 2026 Production Field Guide — hero image
#flutter#mobile-development

Flutter Mobile App Development: A 2026 Production Field Guide

How we structure Flutter projects at GetWidget in 2026: feature-first layout, Riverpod defaults, Dart 3 records and sealed classes, Material 3 theming, the 200-line widget rule, performance diagnosis, CI/CD pipelines, and the production pitfalls that bite teams after launch.

Navin Sharma Navin Sharma
12m
Flutter ListTile Widget: M3 Properties, Theming and Migration (2026) — hero image
#flutter#listtile

Flutter ListTile Widget: M3 Properties, Theming and Migration (2026)

The flutter listtile widget patterns we ship in production: 30+ constructor parameters worth knowing, Material 3 defaults that shifted, ListTileTheme inheritance, performance rules for long lists and the five ListTile bugs we catch in code review.

Navin Sharma Navin Sharma
6m
Back to Blog