Skip to content
Blog

CarouselView in Flutter: Infinite Scrolling and State Callbacks

A hands-on guide to Flutter's Material CarouselView: weighted layouts, snapping, the new infinite looping mode, and tracking the leading item with onIndexChanged and CarouselController.

Published on • October 3, 2026

AI Assistant

Promotional banners, product rails, onboarding pages, testimonial strips — horizontal carousels are everywhere, and for years Flutter developers hand-rolled them with PageView plus a dot indicator. CarouselView is the Material 3 answer: a carousel that handles sizing, snapping, splashes and paging for you.

Sources: What’s new in Flutter 3.44, CarouselView API, CarouselController API.

The basics

CarouselView requires an itemExtent — the main-axis size of each item — and a list of children. Everything else is optional:

CarouselView(
  itemExtent: 280,
  itemSnapping: true,
  shrinkExtent: 40,
  children: [
    for (final banner in banners) BannerCard(banner: banner),
  ],
)

Two parameters do most of the visual work:

  • itemExtent — the fixed size of an item in the scroll direction. Required; there is no “measure it for me” mode.
  • shrinkExtent — how small adjacent items may get during the scroll transition (default 0.0). Setting it to 40 keeps a sliver of the neighbours visible, which is what makes a carousel read as a carousel instead of a slideshow.

There are four constructors for different data shapes: the default CarouselView (a children list), CarouselView.builder (itemBuilder + itemCount), and the weighted / weightedBuilder variants that take flexWeights — a list of ints giving each item a relative width, for hero-and-teasers layouts. consumeMaxWeight (default true) controls how leftover space is distributed.

Looping: the infinite flag

Flutter 3.44 added looping to CarouselView (PR #175710) as a plain constructor flag:

CarouselView(
  itemExtent: 320,
  infinite: true,          // continuous scrolling in both directions
  itemSnapping: true,
  children: banners,
)

infinite defaults to false. When enabled, scrolling past the last item wraps to the first and vice versa — no more duplicating your list to fake a loop, and no seam where the carousel “runs out”.

Practical notes:

  • Pair it with itemSnapping: true so the loop settles on a whole item rather than between two.
  • Your index indicator now needs modular arithmetic: (index + 1) % banners.length for a “2/8” label.
  • If the carousel is data-driven, make sure your builder can render itemCount items — the flag loops the viewport, not your data source.

Tracking state: onIndexChanged and CarouselController

Two additions from the same release (PR #180667) make state observable:

final controller = CarouselController();

CarouselView(
  controller: controller,
  itemExtent: 300,
  infinite: true,
  onIndexChanged: (index) => setState(() => _page = index),
  children: banners,
)
  • onIndexChanged — ValueChanged<int>?, invoked when the leading item changes. This is the hook for updating page dots, analytics, or prefetching the next banner’s image.
  • CarouselController.leadingItem — a getter returning the current leading item index. Useful when you need the value now (for example when building a label in build) rather than in a callback.
  • CarouselController.animateToItem(index) — jumps the carousel to an item (default 300 ms, Curves.ease), which is how you wire dot indicators back to taps.

CarouselController extends ChangeNotifier and ScrollController, so familiar offset-based APIs still work for low-level needs — but animateToItem plus leadingItem covers the indicator use case without touching offsets.

Other knobs worth knowing

ParameterPurpose
itemSnappingSettle on item boundaries instead of free-scrolling
shrinkExtentMinimum size of off-screen items during transitions
padding, backgroundColor, elevation, shapeMaterial surface treatment of the carousel itself
itemClipBehavior, overlayColorHow items are clipped and tinted when pressed
onTapValueChanged<int> for taps on an item
enableSplashToggle the Material splash on tap (default true)
scrollDirection, reverseVertical carousels are a parameter away
padding around itemsSpace surrounding each carousel item

On desktop, mouse drag is disabled by default — users scroll with Shift + wheel — so don’t rely on drag-only affordances when your carousel ships on Windows, Linux or macOS.

When not to use it

CarouselView is opinionated: fixed itemExtent, Material chrome, index-based callbacks. Reach for PageView.builder when you need full-page snapping with variable sizes, a custom scroll physics, or a non-Material surface; reach for a CustomScrollView with SliverList when the carousel is part of a larger scrolling composition and you want it to collapse on scroll.

For the classic “banner rail with dots”, though, CarouselView — especially with infinite, itemSnapping and onIndexChanged — is now the shortest correct path, and it removes the two bugs that hand-rolled versions always ship with: the loop seam and the indicator that’s one index behind.