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 (default0.0). Setting it to40keeps 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: trueso the loop settles on a whole item rather than between two. - Your index indicator now needs modular arithmetic:
(index + 1) % banners.lengthfor a “2/8” label. - If the carousel is data-driven, make sure your builder can render
itemCountitems — 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 inbuild) 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
| Parameter | Purpose |
|---|---|
itemSnapping | Settle on item boundaries instead of free-scrolling |
shrinkExtent | Minimum size of off-screen items during transitions |
padding, backgroundColor, elevation, shape | Material surface treatment of the carousel itself |
itemClipBehavior, overlayColor | How items are clipped and tinted when pressed |
onTap | ValueChanged<int> for taps on an item |
enableSplash | Toggle the Material splash on tap (default true) |
scrollDirection, reverse | Vertical carousels are a parameter away |
padding around items | Space 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.