# flutter_calendar_carousel: a controlled, theme-aware calendar for Flutter

> flutter_calendar_carousel is a Flutter calendar you swipe horizontally, and version 3.0 turned it into a controlled component with a generic event type, lazy month and week paging and grouped configuration objects instead of dozens of constructor parameters. The breaking change is deliberate, and the README explains exactly what was removed.

**hyochan/flutter_calendar_carousel** —  Calendar widget for flutter that is swipeable horizontally. This widget can help you build your own calendar widget highly customizable.

- Repository: https://github.com/hyochan/flutter_calendar_carousel
- Stars: 859 · Forks: 320
- Language: Dart
- License: MIT
- Published: 2026-10-20 · Updated: 2026-10-20 · Language: en
- Canonical page: https://hysenlabs.com/projects/hyochan-flutter-calendar-carousel

## What the package is and what changed in 3.0

flutter_calendar_carousel is an accessible, theme-aware Flutter calendar with lazy month and week paging, controlled dates, typed events and concise customization APIs. The important context is the upgrade note. If you are coming from 2.x, version 3.0 is intentionally breaking: the 2.x compatibility layer, the package-owned event models and the legacy styling flags have all been removed, and the README asks you to review the complete 2.x-to-3.0 migration guide before upgrading. The word to note is intentionally. This is not a rewrite that happened by accident, it is a package that decided the old API was wrong and cut it. The consequences for a reader are that the 3.0 API is the one to learn, and that any tutorial or snippet you find for 2.x will not compile against it.

## Installing it and the Dart floor

The package is on pub.dev and installs with the pub command.

```shell
flutter pub add flutter_calendar_carousel
```

Then you import the material library and the package itself.

```dart
import 'package:flutter/material.dart';
import 'package:flutter_calendar_carousel/flutter_calendar_carousel.dart';
```

The package requires Dart >=3.8.0 <4.0.0, so the upper bound is as real as the lower one. That constraint will matter to you if your application is on an older Flutter, and the reason the constraint exists is visible in the repository: there are separate.claude,.codex and.github directories for assistant configuration plus a GEMINI.md, and a renovate.json for dependency updates, which are the hallmarks of a package that is actively kept current against a moving SDK.

## The calendar is controlled, which changes how you write it

The quick start gives you a stateful widget, and the shape of the code is the lesson. You hold two dates in your state, a selected date and a focused date, and you give the calendar a bounded height.

```dart
class CalendarExample extends StatefulWidget {
  const CalendarExample({super.key});

  @override
  State<CalendarExample> createState() => _CalendarExampleState();
}
```

The selection contract is spelled out precisely. selectedDate determines which day is visually selected, and a tap does not mutate it, so you update it from onDateSelected. focusedDate requests the visible month or week, and you update it for external navigation such as a Today button. onPageChanged reports the first day of the visible month or the configured first day of the visible week. Omit onDateSelected and the dates render without tap actions, and isDateEnabled handles per-date availability while firstDate and lastDate set the navigable range, with the default range spanning 100 years before and after today's civil date. This is the biggest change in spirit from a typical calendar widget: the widget renders and reports, your state decides.

## Events are your type, not the package's

The event model is generic, so you index your own application model by date and resolve it with a callback.

```dart
final Map<DateTime, List<Meeting>> meetingsByDate =
    <DateTime, List<Meeting>>{
      DateTime(2026, 8, 15): const <Meeting>[
        Meeting('Release', Colors.indigo),
      ],
    };

List<Meeting> meetingsForDate(DateTime date) =>
    meetingsByDate[date] ?? const <Meeting>[];
```

And you render the markers yourself.

```dart
Widget buildMeetingCalendar() => CalendarCarousel<Meeting>.month(
  focusedDate: DateTime(2026, 8, 15),
  eventsForDate: meetingsForDate,
  markerBuilder: (BuildContext context, CalendarDayDetails<Meeting> day) {
    return Align(
      alignment: Alignment.bottomCenter,
      child: Icon(Icons.circle, size: 6, color: day.events.first.color),
    );
  },
);
```

Two performance notes are attached to this. eventsForDate can run again during rebuilds, so keep it synchronous, pure and inexpensive. And CalendarDayDetails.events and the list passed to onDateSelected are immutable snapshots. A custom markerBuilder runs once for each visible date with events, and returning null hides that date's marker.

## Markers, semantics and the built-in fallback

Without a custom builder, the built-in marker renders at most theme.marker.maxVisible dots and an optional overflow count. Its English semantic fallback is the phrase for one event or the count and the word for more, and the README is explicit that you should localise that announcement without replacing the visual marker. The mechanism for doing so is a semantic label builder on the marker style.

```dart
final markerTheme = CalendarCarouselThemeData(
  marker: CalendarMarkerStyle(
    semanticLabelBuilder: (BuildContext context, int count) =>
        count == 1 ? '1 appointment' : '$count appointments',
  ),
);
```

That separation is the interesting part of the design. The visible marker and the announcement a screen reader receives are separate concerns, so a translated application can keep the same dots and still announce the count in its own language. It is a small feature, but it makes the accessibility support concrete rather than an afterthought.

## Configuration grouped instead of dozens of parameters

Related options are grouped into configuration objects rather than exposed as a long constructor parameter list.

```dart
Widget buildStyledCalendar() => CalendarCarousel<Meeting>.month(
  header: const CalendarHeaderConfig(
    enableDatePicker: true,
    showNavigationButtons: true,
  ),
  weekdays: const CalendarWeekdayConfig(
    format: CalendarWeekdayFormat.short,
  ),
  layout: const CalendarLayoutConfig(
    fixedSixWeeks: true,
    showOutsideDays: false,
  ),
  paging: const CalendarPagingConfig(axis: Axis.horizontal),
  theme: const CalendarCarouselThemeData(
    selected: CalendarDayStyle(
      backgroundColor: Colors.indigo,
      textStyle: TextStyle(color: Colors.white),
    ),
    marker: CalendarMarkerStyle(color: Colors.deepOrange, maxVisible: 3),
  ),
);
```

The header, the weekday row, the layout, the paging and the theme each have their own object, so the constructor call reads as a list of concerns rather than an alphabet of flags. The theming section then explains how the pieces interact, with the day style priority running base to weekend to outside month to with events to dayStyleResolver to today to disabled to selected, and partial styles inheriting unspecified values from the ambient Material theme.

## The paging edge case and the day builder

Two details in the README are worth knowing before you build. Outside-month position and styling apply only to month pages, and every date in a seven-day week page reports CalendarMonthPosition.current, so a day that spills from the previous month looks different depending on which view it is in. And when paging.viewportFraction is below one, month pages suppress outside-month dates even if showOutsideDays is true, because otherwise a page and its visible preview would render the same civil date twice. That is a bug class the package closes for you rather than leaving you to reason about. For content that is more than a colour, dayBuilder replaces the visual content of the day entirely.

```dart
final CalendarDayBuilder<Meeting> meetingDayBuilder =
    (BuildContext context, CalendarDayDetails<Meeting> day) {
      if (day.events.isEmpty) return null;
      return Center(
        child: Text('${day.date.day} ★', style: day.style.textStyle),
      );
    };
```

Returning null from dayBuilder keeps the default day number, which is how you change only the days that need changing.

## Where flutter_calendar_carousel is the wrong choice

Four limits apply. The widget does not size itself, so a calendar in an unbounded layout needs a SizedBox around it, which is a small thing but the kind of small thing that costs an hour the first time. Because selection is controlled, you cannot drop it into a page and have it work: you have to hold the state, wire the callbacks and decide what happens on a tap, which is the right design for a real application and the wrong one for a quick demo. The controlled model also means two dates to keep in sync, and the package does not resolve the case where they disagree, since selectedDate decides what is highlighted and focusedDate decides what is visible, and those are separate decisions. And version 3 is young, with v3.0.0 published on 2026-08-09 and two patch releases since, so the API is still settling and the migration guide is the document to keep.

## Conclusion

Use flutter_calendar_carousel when you need a calendar you can actually style in a Flutter application, since version 3 gives you typed events, a marker builder and a day builder rather than a fixed set of widgets. Do not adopt it without reading the 2.x to 3.0 migration guide, because the compatibility layer, the package-owned event models and the legacy styling flags are all gone. Verify first that your Dart version is 3.8.0 or newer, that you are prepared to hold the selection in your own widget state, since a tap does not mutate the calendar, and that your screen gives the widget a bounded height, because the carousel does not size itself.

## FAQ

### How do I add flutter_calendar_carousel to a Flutter project?

Run flutter pub add flutter_calendar_carousel, then import package:flutter/material.dart and package:flutter_calendar_carousel/flutter_calendar_carousel.dart. The package requires Dart >=3.8.0 <4.0.0.

### What changed in version 3.0 of flutter_calendar_carousel?

It is intentionally breaking. The 2.x compatibility layer, the package-owned event models and the legacy styling flags have been removed, and the README asks you to review the 2.x-to-3.0 migration guide before upgrading. Releases include v3.0.0 from 2026-08-09, v3.0.1 from 2026-09-15 and v3.0.2 from 2026-09-21.

### How does the calendar handle selection in flutter_calendar_carousel?

Both dates are controlled values held in your widget state. selectedDate determines which day is visually selected and a tap does not mutate it, so you update it from onDateSelected, while focusedDate requests the visible month or week and is updated for external navigation such as a Today button.

### Can I show my own event types in flutter_calendar_carousel?

Yes. The calendar is generic over the event type, so you index your own model by local date-only keys and resolve them with an eventsForDate callback, then render markers with markerBuilder or replace day content with dayBuilder. The README advises keeping eventsForDate synchronous, pure and inexpensive because it can run again during rebuilds.

### What licence is flutter_calendar_carousel released under?

MIT, with the LICENSE file at the repository root and a matching badge in the README. The package is published on pub.dev under the name flutter_calendar_carousel.

## Sources

- [hyochan/flutter_calendar_carousel on GitHub](https://github.com/hyochan/flutter_calendar_carousel)
- [Issues](https://github.com/hyochan/flutter_calendar_carousel/issues)
- [License: MIT](https://github.com/hyochan/flutter_calendar_carousel/blob/main/LICENSE)
- [README](https://github.com/hyochan/flutter_calendar_carousel/blob/main/README.md)
- [Releases](https://github.com/hyochan/flutter_calendar_carousel/releases)

---

Hysen Labs editorial analysis, written from the project's own repository and release notes. Cite the canonical page: https://hysenlabs.com/projects/hyochan-flutter-calendar-carousel
