Featured

Flutter Image Slider with Dots and Autoplay: PageView, CarouselView or carousel_slider

Updated
Advertisement

The quickest Flutter image slider is the carousel_slider package (5.1.2): set autoPlay: true and add smooth_page_indicator 3.0.0 for dots. Want no dependency? Use PageView for full-width banners, or the built-in Material 3 CarouselView (Flutter 3.24+) for slides that peek at the next one. Analyzed code for all three is below, checked on 3 October 2026.

Flutter logo on a phone beside the title Image Slider in Flutter
Image slider in Flutter

In a hurry? Jump to: which one to use · PageView with dots and autoplay · carousel_slider · CarouselView · network images · responsive · before/after slider · not working?

Which Flutter image slider should you use?

Three approaches cover almost every case, and a newer all-in-one package is listed too. Versions and dates below were checked on 3 October 2026; Flutter 3.47.6 (released 1 October 2026) is the current stable release.

ApproachAutoplayDotsPick it when
PageView (built in)Timer + nextPage()Your own, or a packageFull-width banners, no dependency
CarouselView (built in, Flutter 3.24+)None built inNone built inMaterial 3 cards that peek at the next
carousel_slider 5.1.2autoPlay: trueNone built inLeast code for autoplay and looping
flutter_carousel_widget 4.0.1Built inBuilt inAll-in-one, but needs Flutter 3.47+

Short version: carousel_slider for the least code (it is the most downloaded carousel package in this comparison, about 917,000 downloads in 30 days), PageView when you do not want a package to maintain, and CarouselView when the design is Material 3 and slides should peek at the next one. Check any older package before adopting it: card_swiper has had no release since July 2023, and carousel_pro, still recommended by older tutorials, was last released in June 2019 and will not install on Dart 3.

Looking for a draggable value slider (volume, brightness) instead? That is Flutter's Slider widget, not an image carousel. Our SeekBar tutorial covers the Android equivalent.

Advertisement

How to make a Flutter image slider with PageView (dots and autoplay)

This version has no dependency and works as a self-advancing slideshow. It loops forward forever, pauses while a finger is on the slider, cancels its Timer when the screen goes away, and keeps the dots in sync. Paste it into lib/main.dart and run it on a device or emulator; the Android Studio emulator is covered in our Android emulator guide, and there is an Apple Silicon Mac version.

import 'dart:async';

import 'package:flutter/material.dart';

void main() => runApp(
  const MaterialApp(
    home: Scaffold(
      body: SafeArea(child: Center(child: PageViewSlider())),
    ),
  ),
);

const _slides = <String>[
  'https://picsum.photos/id/1015/1200/675',
  'https://picsum.photos/id/1016/1200/675',
  'https://picsum.photos/id/1018/1200/675',
];

class PageViewSlider extends StatefulWidget {
  const PageViewSlider({super.key});

  @override
  State<PageViewSlider> createState() => _PageViewSliderState();
}

class _PageViewSliderState extends State<PageViewSlider> {
  // Start past page 0 so the user can also swipe backwards around the loop.
  final _controller = PageController(
    viewportFraction: 0.9,
    initialPage: _slides.length * 100,
  );
  Timer? _timer;
  int _dot = 0;

  @override
  void initState() {
    super.initState();
    _startAutoplay();
  }

  void _startAutoplay() {
    _timer?.cancel();
    _timer = Timer.periodic(const Duration(seconds: 3), (_) {
      if (_controller.hasClients) {
        _controller.nextPage(
          duration: const Duration(milliseconds: 500),
          curve: Curves.easeInOut,
        );
      }
    });
  }

  @override
  void dispose() {
    _timer?.cancel(); // without this the timer outlives the screen
    _controller.dispose();
    super.dispose();
  }

  @override
  Widget build(BuildContext context) {
    return Column(
      mainAxisSize: MainAxisSize.min,
      children: [
        AspectRatio(
          aspectRatio: 16 / 9,
          child: Listener(
            // Pause while a finger is down, resume when it lifts.
            onPointerDown: (_) => _timer?.cancel(),
            onPointerUp: (_) => _startAutoplay(),
            onPointerCancel: (_) => _startAutoplay(),
            child: PageView.builder(
              controller: _controller,
              onPageChanged: (page) =>
                  setState(() => _dot = page % _slides.length),
              itemBuilder: (context, page) => Padding(
                padding: const EdgeInsets.symmetric(horizontal: 4),
                child: ClipRRect(
                  borderRadius: BorderRadius.circular(12),
                  child: Image.network(
                    _slides[page % _slides.length],
                    fit: BoxFit.cover,
                    errorBuilder: (context, error, stackTrace) =>
                        const Center(child: Icon(Icons.broken_image_outlined)),
                  ),
                ),
              ),
            ),
          ),
        ),
        const SizedBox(height: 12),
        Row(
          mainAxisAlignment: MainAxisAlignment.center,
          children: [
            for (var i = 0; i < _slides.length; i++)
              AnimatedContainer(
                duration: const Duration(milliseconds: 250),
                margin: const EdgeInsets.symmetric(horizontal: 4),
                width: i == _dot ? 20 : 8,
                height: 8,
                decoration: BoxDecoration(
                  color: i == _dot
                      ? Theme.of(context).colorScheme.primary
                      : Colors.grey.shade400,
                  borderRadius: BorderRadius.circular(4),
                ),
              ),
          ],
        ),
      ],
    );
  }
}
  • Looping. itemCount is left null and each slide is picked with page % _slides.length, so autoplay always moves forward instead of rewinding through every slide. Starting at _slides.length * 100 lets people swipe backwards too.
  • Pausing. The Listener cancels the timer when a finger touches down and restarts it on release. carousel_slider does this for you.
  • Cleanup. dispose() cancels the timer. Without that line the periodic timer keeps firing after the screen is gone.
Advertisement

Add both packages with flutter pub add carousel_slider smooth_page_indicator, which picks versions your SDK supports. As of 3 October 2026 the latest are carousel_slider 5.1.2 (published 5 February 2026) and smooth_page_indicator 3.0.0 (21 August 2026).

import 'package:carousel_slider/carousel_slider.dart';
import 'package:flutter/material.dart';
import 'package:smooth_page_indicator/smooth_page_indicator.dart';

const _slides = <String>[
  'https://picsum.photos/id/1015/1200/675',
  'https://picsum.photos/id/1016/1200/675',
  'https://picsum.photos/id/1018/1200/675',
];

class PackageSlider extends StatefulWidget {
  const PackageSlider({super.key});

  @override
  State<PackageSlider> createState() => _PackageSliderState();
}

class _PackageSliderState extends State<PackageSlider> {
  final _controller = CarouselSliderController(); // not CarouselController
  final _index = ValueNotifier<int>(0); // only the dots rebuild, not the slider

  @override
  void dispose() {
    _index.dispose();
    super.dispose();
  }

  @override
  Widget build(BuildContext context) {
    return Column(
      mainAxisSize: MainAxisSize.min,
      children: [
        CarouselSlider.builder(
          carouselController: _controller,
          itemCount: _slides.length,
          itemBuilder: (context, index, pageViewIndex) => ClipRRect(
            borderRadius: BorderRadius.circular(12),
            child: Image.network(
              _slides[index],
              fit: BoxFit.cover,
              width: double.infinity,
              errorBuilder: (context, error, stackTrace) =>
                  const Center(child: Icon(Icons.broken_image_outlined)),
            ),
          ),
          options: CarouselOptions(
            aspectRatio: 16 / 9,
            viewportFraction: 0.9,
            autoPlay: true,
            autoPlayInterval: const Duration(seconds: 3),
            enlargeCenterPage: true,
            onPageChanged: (index, reason) => _index.value = index,
          ),
        ),
        const SizedBox(height: 12),
        ValueListenableBuilder<int>(
          valueListenable: _index,
          builder: (context, active, _) => AnimatedSmoothIndicator(
            activeIndex: active,
            count: _slides.length,
            onDotClicked: (index) => _controller.animateToPage(index),
            effect: const WormEffect(
              dotWidth: 8,
              dotHeight: 8,
              dotColor: Colors.black26,
              activeDotColor: Colors.indigo,
            ),
          ),
        ),
      ],
    );
  }
}

Details that trip people up:

  • Use CarouselSliderController, not CarouselController. Version 5.0.0 (August 2024) renamed it, days after Flutter 3.24 added its own CarouselController for CarouselView. Code written for carousel_slider 4.x that uses CarouselController fails on Flutter 3.24 or newer with an ambiguous-import error.
  • Keep the active index in a ValueNotifier. carousel_slider creates a new internal PageController whenever its parent rebuilds, so setState inside onPageChanged replaces the controller on every slide change. The notifier rebuilds only the dots.
  • Set the dot colors yourself. smooth_page_indicator 3.0.0 no longer reads the app theme; its fallback colors are fixed indigo and gray. Pass activeDotColor and dotColor to the effect.
  • height beats aspectRatio. Set both and aspectRatio is ignored.

Defaults worth knowing in 5.1.2: viewportFraction 0.8, aspectRatio 16/9, autoPlayInterval 4 seconds, autoPlayAnimationDuration 800 ms, enableInfiniteScroll true, pauseAutoPlayOnTouch true, enlargeCenterPage false. The dots_indicator package (5.0.0, 16 September 2026) also draws dots, but it needs Flutter 3.44 or newer.

Maintenance: the last release, 5.1.2 on 5 February 2026, fixed a PageController leak. The repository is not archived and lists 216 open issues and pull requests (checked 3 October 2026).

Advertisement

Material 3 CarouselView: the built-in carousel

CarouselView lives in Flutter's Material library, so it needs no package. CarouselView.weighted sizes slides by proportion: weights of [7, 1] give one big slide and a peek of the next at any screen width. The example switches to [3, 3, 3, 1] from 700 dp up.

import 'package:flutter/material.dart';

const _slides = <String>[
  'https://picsum.photos/id/1015/1200/675',
  'https://picsum.photos/id/1016/1200/675',
  'https://picsum.photos/id/1018/1200/675',
];

class MaterialCarousel extends StatelessWidget {
  const MaterialCarousel({super.key});

  @override
  Widget build(BuildContext context) {
    return LayoutBuilder(
      builder: (context, constraints) {
        // Weights are proportions, so the slides scale with the screen.
        final wide = constraints.maxWidth >= 700;
        return ConstrainedBox(
          // CarouselView scrolls, so it needs a bounded height.
          constraints: BoxConstraints(maxHeight: wide ? 240 : 220),
          child: CarouselView.weighted(
            flexWeights: wide ? const [3, 3, 3, 1] : const [7, 1],
            itemSnapping: true,
            onTap: (index) => debugPrint('Tapped slide $index'),
            children: [
              for (final url in _slides)
                Image.network(
                  url,
                  fit: BoxFit.cover,
                  errorBuilder: (context, error, stackTrace) =>
                      const Center(child: Icon(Icons.broken_image_outlined)),
                ),
            ],
          ),
        );
      },
    );
  }
}

Which Flutter release added what, from the framework source at each release tag and the official release notes:

Flutter releaseCarouselView gained
3.24 (6 Aug 2024)CarouselView with a fixed itemExtent, plus CarouselController
3.27 (11 Dec 2024)CarouselView.weighted and flexWeights
3.32 (20 May 2025)CarouselController.animateToItem
3.41 (11 Feb 2026)CarouselView.builder and .weightedBuilder
3.44 (18 May 2026)infinite, onIndexChanged, CarouselController.leadingItem
  • No autoplay, no dots. For autoplay, call CarouselController.animateToItem from a Timer. For dots, onIndexChanged reports the leading (first visible) item and fires only once that item has fully left the viewport, so it is most useful with a hero layout such as [7, 1] or [1].
  • One full-width slide per page: use weights of [1].
  • Bounded height required. CarouselView scrolls, so inside a Column it throws the same unbounded-height error as PageView. The example wraps it in ConstrainedBox.

Heads-up: Material is moving into its own package. Flutter 3.47 (12 August 2026) lets you opt in to package:material_ui/material_ui.dart. Flutter's migration guide says the in-SDK Material library has been frozen since 3.44 and is scheduled for formal deprecation in an upcoming stable release. The snippets here use package:flutter/material.dart, which Flutter 3.47.6 still ships; to migrate later, run dart fix --apply --code=migrate_design_widgets.

Network images, assets and caching

All three sliders accept any widget. Use Image.network for URLs and Image.asset for bundled files, which you list under flutter: assets: in pubspec.yaml. Keep an errorBuilder on network images, as the examples do, and add a loadingBuilder if you want a spinner. For slides that reappear often, cached_network_image keeps them on disk:

import 'package:cached_network_image/cached_network_image.dart';

// In an itemBuilder, instead of Image.network:
CachedNetworkImage(
  imageUrl: url,
  fit: BoxFit.cover,
  placeholder: (context, url) =>
      const Center(child: CircularProgressIndicator()),
  errorWidget: (context, url, error) =>
      const Center(child: Icon(Icons.broken_image_outlined)),
)

Version note: cached_network_image 4.0 (August 2026) requires Flutter 3.44 or newer and Dart 3.12, because it now depends on material_ui. On an older SDK, flutter pub add cached_network_image falls back to 3.4.1 (August 2024), which uses the same API as the snippet above. The photo URLs in the examples are placeholders so the code shows pictures on first run; replace them with your own.

Advertisement

Make a Flutter image slider responsive

Size slides by ratio and let the width come from the layout:

  • PageView: wrap it in AspectRatio(aspectRatio: 16 / 9). PageController.viewportFraction is final, so changing it at a breakpoint means creating a new controller.
  • carousel_slider: set aspectRatio instead of height, and change viewportFraction from a LayoutBuilder. The package re-applies both when they change, including on window resize.
  • CarouselView.weighted: weights are proportions, so slides already scale with the width. Switch the weights at a breakpoint with LayoutBuilder, as in the example above.

Before and after (image compare) slider in Flutter

A compare slider is a different widget from a carousel: two images are stacked and a draggable divider decides how much of the top one shows. This version uses only Flutter widgets. The top image is clipped to a fraction of the width that follows the finger.

import 'package:flutter/material.dart';

/// Before/after slider: drag the handle to reveal more of one image.
class CompareSlider extends StatefulWidget {
  const CompareSlider({super.key, required this.before, required this.after});

  final ImageProvider before;
  final ImageProvider after;

  @override
  State<CompareSlider> createState() => _CompareSliderState();
}

class _CompareSliderState extends State<CompareSlider> {
  double _split = 0.5; // share of the width that shows "before"

  @override
  Widget build(BuildContext context) {
    return AspectRatio(
      aspectRatio: 16 / 9,
      child: LayoutBuilder(
        builder: (context, box) => GestureDetector(
          onHorizontalDragUpdate: (details) => setState(() {
            _split = (details.localPosition.dx / box.maxWidth).clamp(0.0, 1.0);
          }),
          child: Stack(
            fit: StackFit.expand,
            children: [
              Image(image: widget.after, fit: BoxFit.cover),
              ClipRect(
                clipper: _SplitClipper(_split),
                child: Image(image: widget.before, fit: BoxFit.cover),
              ),
              Positioned(
                left: box.maxWidth * _split - 18,
                top: 0,
                bottom: 0,
                width: 36,
                child: Stack(
                  alignment: Alignment.center,
                  children: [
                    Container(width: 3, color: Colors.white),
                    const CircleAvatar(
                      radius: 18,
                      backgroundColor: Colors.white,
                      child: Icon(Icons.compare_arrows, color: Colors.black87),
                    ),
                  ],
                ),
              ),
            ],
          ),
        ),
      ),
    );
  }
}

class _SplitClipper extends CustomClipper<Rect> {
  const _SplitClipper(this.split);

  final double split;

  @override
  Rect getClip(Size size) =>
      Rect.fromLTWH(0, 0, size.width * split, size.height);

  @override
  bool shouldReclip(_SplitClipper oldClipper) => oldClipper.split != split;
}

Use it as CompareSlider(before: AssetImage('assets/before.jpg'), after: AssetImage('assets/after.jpg')). If you need zoom, pan or rotation, image_compare_slider 3.2.0 (30 June 2026) offers them according to its pub.dev listing; before_after 3.2.0 dates from November 2024.

Advertisement

Why is my Flutter image slider not working?

Most failures come down to a handful of causes. Messages were reproduced on Flutter 3.38.1 unless noted.

What you seeCauseFix
Horizontal viewport was given unbounded heightPageView or CarouselView in a ColumnWrap in AspectRatio or SizedBox
The name 'CarouselController' is defined in the libraries…carousel_slider 4.x on Flutter 3.24+Use 5.x and CarouselSliderController
Images blank in a release build or on Android 9+No INTERNET permission, or an http:// URLAdd the permission to the main manifest; use https
PageController.page cannot be accessed before a PageView is built with itTimer outlived the screenCancel it in dispose()
Will not scroll with a mouse (web, desktop)Mouse is not a default drag deviceSet scrollBehavior (below)
carousel_pro… doesn't support null safety from pub addOlder tutorial, 2019 packageUse carousel_slider 5.x or PageView
Dots out of step with the slideUsing the raw page numberUse page % length or the onPageChanged index
Dot colors changed after an upgradesmooth_page_indicator 3.0.0 ignores the app themeSet dotColor and activeDotColor
The named parameter 'infinite' isn't defined, or pub add failsFlutter older than the API or package needsUpgrade Flutter; let flutter pub add choose versions
Assertion firstIndex == 0 || childScrollOffset… on some Android phonesOpen reports: infinite scroll with viewportFraction below 1Try enableInfiniteScroll: false; not reproduced here

Two notes on the table. The Android permission is easy to miss because debug and profile manifests in a new Flutter project already declare INTERNET while the main manifest does not, and Android 9 (API 28) blocks cleartext HTTP by default. With enableInfiniteScroll: false, carousel_slider autoplay sweeps back through the slides to the first one instead of looping forward. For the mouse fix, set the scroll behavior once on MaterialApp:

// needs: import 'package:flutter/gestures.dart';
MaterialApp(
  scrollBehavior: const MaterialScrollBehavior().copyWith(
    dragDevices: {PointerDeviceKind.touch, PointerDeviceKind.mouse},
  ),
  home: const MyHomePage(),
)

How we picked and verified this

The three approaches were compared on four criteria: it works on the current Flutter release, it is maintained, it needs few or no dependencies, and it handles dots and autoplay without hacks. What was checked on 3 October 2026:

  • Versions and maintenance. Package versions, release dates and SDK limits came from pub.dev's public data, together with the carousel_slider changelog, source and issue tracker.
  • Flutter itself. The current stable release (3.47.6, 1 October 2026) came from Flutter's release feed. The CarouselView history comes from the framework source at the 3.24.0, 3.27.0, 3.32.0, 3.41.0, 3.44.0 and 3.47.6 tags and the official release notes. The Material change comes from Flutter's own migration page.
  • Code. Every snippet was analyzed with flutter analyze (no issues) on Flutter 3.38.1 and Dart 3.10.0 and exercised in automated widget tests: autoplay timing, dot sync and looping, pause on touch, timer cleanup, a phone-to-tablet resize and the compare-slider drag. The error messages in the table were reproduced on the same SDK.
  • Limits. Nothing was run on a physical phone or emulator, and nothing was compiled against Flutter 3.47. CarouselView.builder, infinite, onIndexChanged, cached_network_image 4.x, dots_indicator 5.0 and flutter_carousel_widget 4.0 are described from Flutter's source, package changelogs and pub.dev data only. The Android assertion in the last table row is reported by other developers and was not reproduced here.

Bottom line

For a home-screen banner with autoplay and dots, use carousel_slider 5.1.2 with smooth_page_indicator 3.0.0 if you want the least code, or the PageView version if you would rather not depend on a package. Choose CarouselView.weighted when the design is Material 3 and slides should peek at the next one. Whichever you pick, give the slider a bounded height, cancel any Timer in dispose(), and add the INTERNET permission before shipping a release build that loads network images.

Frequently Asked Questions

How do I make an image slider in Flutter?

Use PageView, which is built in, or a carousel package. For the fastest route run flutter pub add carousel_slider, then wrap your images in CarouselSlider with autoPlay: true; carousel_slider 5.1.2 (5 February 2026) is the current release. Without a package, put the images in a PageView.builder inside an AspectRatio and drive the dots from onPageChanged. Complete, analyzed code for both is above.

How do I add a dots indicator to a Flutter image slider?

Neither carousel_slider nor CarouselView draws dots. With carousel_slider, keep the index from onPageChanged in a ValueNotifier and show smooth_page_indicator 3.0.0's AnimatedSmoothIndicator. With PageView, set the active dot from onPageChanged using page % length. From smooth_page_indicator 3.0.0 (21 August 2026), set dotColor and activeDotColor yourself, because it no longer reads the app theme.

PageView, CarouselView or carousel_slider: which should you use for an image slider?

Use carousel_slider 5.1.2 for the least code with autoplay and looping, PageView for full-width pages with no dependency, and CarouselView (Flutter 3.24+) for Material 3 layouts where slides peek at the next one. CarouselView has no autoplay or dots built in. flutter_carousel_widget 4.0.1 includes both but needs Flutter 3.47 or newer.

How do I make a Flutter image slider scroll automatically?

With carousel_slider set autoPlay: true and autoPlayInterval; it pauses on touch by default. With PageView use a Timer.periodic that calls PageController.nextPage, cancel it on pointer down, and cancel it again in dispose(). CarouselView has no autoplay, so call CarouselController.animateToItem from a Timer; that method needs Flutter 3.32 or newer.

Why is my Flutter image slider not working?

The usual causes are no bounded height (PageView throws 'Horizontal viewport was given unbounded height'), a missing Android INTERNET permission in release builds, http:// image URLs blocked on Android 9 and newer, carousel_slider 4.x clashing with Flutter's CarouselController (use 5.x and CarouselSliderController), or mouse dragging being off by default on web and desktop. The fix table above covers each.

Does Flutter have a built-in carousel widget?

Yes. CarouselView is a Material 3 widget that arrived in Flutter 3.24 on 6 August 2024. CarouselView.weighted followed in 3.27, CarouselView.builder in 3.41, and infinite looping plus onIndexChanged in 3.44. It has no autoplay or dot indicator. As of 3 October 2026 the current stable release is Flutter 3.47.6.

How do I make a responsive image slider in Flutter?

Size the slider by ratio, not pixels: wrap PageView in AspectRatio(aspectRatio: 16 / 9) or set CarouselOptions(aspectRatio: 16 / 9) without also setting height. With CarouselView.weighted the flexWeights are proportions, so slides scale with the screen width; switch them with LayoutBuilder, for example [7, 1] on phones and [3, 3, 3, 1] from 700 dp up.

How do I build a before and after image slider in Flutter?

Stack two images, clip the top one to a fraction of the width with ClipRect and a CustomClipper, and update that fraction from onHorizontalDragUpdate. The CompareSlider code above does this in about 70 lines with no package. Packages such as image_compare_slider 3.2.0 (30 June 2026) add zoom and rotation if you need them.

Android DevelopmentCodingFlutter

Related Articles

Advertisement