This Flutter package offers a Calendar Widget featuring a Day, MultiDay, Month and Schedule views. Moreover, it empowers you to tailor the visual and behavioral aspects of the calendar.

Web Demo

Try it out here

Features

  • Views: Day, Multi-day, Month and Schedule. find out more
  • Reschedule: Drag and Drop events.
  • Resize: Resize events on desktop and mobile.
  • Controllers: Manage your calendar with these controllers. find out more
  • Behavior: Choose how you want to handle interaction with the calendar. find out more
  • Appearance: Customize the default components or provide custom builders. find out more
  • Event layout: Use a provided layout strategy or create a custom one. find out more

Roadmap

Features

  • Directionality: Right to Left directionality for default widgets.
  • Views: FreeScroll calender view.

Examples

  • Event layout: Examples of how to leverage this to achieve specific layouts.
  • Repeating Events: Repeating events that only have to be added once.

Preview

MultiDayView

Displays one or more days with time on the vertical axis, ideal for detailed scheduling.

MonthView

Shows an entire month at a glance, with days arranged horizontally.

ScheduleView

Presents events in a continuous, scrollable list, focusing on upcoming or grouped events rather than a grid.

Basic Usage

A minimal example to get you started:

final eventsController = DefaultEventsController();
final calendarController = CalendarController();

/// Add a [CalendarEvent] to the [EventsController].
void addEvents() {
  eventsController.addEvent(CalendarEvent(
    dateTimeRange: DateTimeRange(start: now, end: now.add(const Duration(hours: 1))),
    data: "Event 1",
  ));
}

Widget build(BuildContext context) {  
  return CalendarView(
    eventsController: eventsController,
    calendarController: calendarController,
    // The calender widget will automatically display the correct header & body widgets based on the viewConfiguration.
    viewConfiguration: MultiDayViewConfiguration.singleDay(),
    callbacks: CalendarCallbacks(
      onEventTapped: (event, renderBox) => controller.selectEvent(event),
      onEventCreate: (event) => event.copyWith(data: "Some data"),
      onEventCreated: (event) => eventsController.addEvent(event),
    ),
    header: CalendarHeader(),
    body: CalendarBody(),
  );
}

Views

The Kalender package supports three main calendar views (by default), each tailored for different scheduling needs. You can switch between these views by providing the appropriate ViewConfiguration to the CalendarView widget.

When the CalenderView switches between different ViewConfigurations it uses the initialDateSelectionStrategy function defined in the view configuration object, to determine which day(s) should be visible.

MultiDay View

Displays one or more days with time on the vertical axis, ideal for detailed scheduling (e.g., day, week, or custom ranges).

Constructors:

  • MultiDayViewConfiguration.singleDay() – single day view.
  • MultiDayViewConfiguration.week() – full week view.
  • MultiDayViewConfiguration.workWeek() – Monday to Friday.
  • MultiDayViewConfiguration.custom(days: n) – custom number of days.

Month View

Shows an entire month at a glance, with days arranged horizontally and weeks vertically.

Constructors:

  • MonthViewConfiguration.singleMonth()

Schedule View

Presents events in a continuous, scrollable list, focusing on upcoming or grouped events rather than a grid.

Constructors:

  • ScheduleViewConfiguration.continuous() – single, continuous list of events.
  • ScheduleViewConfiguration.paginated() – paginated by month.

Controllers

The two controllers EventsController and CalendarController do what their names imply.

EventsController

The EventsController manages and exposes events to calendar widgets. Usually there will only be one of these per app. It has a few functions to manipulate events:

  • addEvent Add a new event.
  • addEvents Add multiple new events.
  • removeEvent Remove a event.
  • removeEvents Removes a list of events.
  • removeWhere Remove events where they match a test case.
  • updateEvent Updates an event.
  • byId Returns an event with the given id if it exists.
  • clearEvents Clear all the stored events.
  • eventsFromDateTimeRange Returns events that occur during the given dateTimeRange.

CalendarController

The CalendarController allows you to manage a single calendar widget.

It exposes details about what the widget is displaying.

  • visibleDateTimeRange: A ValueNotifier containing the DateTimeRange that is currently visible.
  • visibleEvents: A ValueNotifier that contains the CalendarEvents that are currently visible.
  • selectedEvent: A ValueNotifier that contains the selected CalendarEvent.

The selectedEvent is the event that currently has focus within the calendar widget. This results in the TileComponents.dropTargetTile being rendered on top of the selected event's widget, on mobile if a event is selected it wil render the resize handles as well.

This controller has a few functions for navigating the calendar widget:

  • jumpToPage: Jump to a specific page.
  • jumpToDate: Jump to a specific date.
  • animateToNextPage: Animate to the next page.
  • animateToPreviousPage: Animate to the previous page.
  • animateToDate: Animate to the given date.
  • animateToDateTime: Animate to the given date time.
  • animateToEvent Animate to the given event.

The CalendarController uses a ViewController internally, which provides navigation and state management for a specific calendar view type (MultiDay, Month, or Schedule).

There are specialized implementations of ViewController for each view:

Each of these controllers works with a corresponding ViewConfiguration (such as MultiDayViewConfiguration, MonthViewConfiguration, or ScheduleViewConfiguration) to handle view-specific logic and behaviors.

Behavior

Callbacks

The calendar has a few useful callback functions, which can change how interactions with the calendar widget is handled.

CalendarCallbacks details
CalendarCallbacks(
  // Called when an event is tapped.
  onEventTapped: (event, renderBox) {},

  // Called when an event is about to be created.
  onEventCreate: (event) {
    // This allows you to modify the event before it is created.
    return event.copyWith(data: data);
  }
  
  // Called when a new event has been created.
  onEventCreated: (event) {
    // Add the event to the eventsController.
    eventsController.addEvent(event);
  },

  // Called before a event is changed.
  onEventChange: (event) {}

  // Called when a event has been changed (rescheduled / resized)
  onEventChanged: (event, updatedEvent) {
    // Do something with the updated event.
    // ex. Update it in your database/long term storage.
  },

  // Called when a page is changed.
  //
  // Alternatively you can listen to the [CalendarController.visibleDateTimeRange] for updates.
  onPageChanged: (visibleDateTimeRange) {},

  /// Called when a user taps on the calendar (Multiday body).
  onTapped(date) {},

  /// Called when a user taps on the calendar (Multiday header / Month body).
  onMultiDayTapped(dateRange) {},
)

Header and Body

The CalendarHeader and CalendarBody both take configuration object's for the different ViewConfigurations. Some behaviors that can be customized:

  • Page/Scroll navigation when rescheduling events.
  • Page/Scroll physics.
  • Event layout strategy.

The CalendarHeader and CalendarBody also take a CalendarInteraction ValueNotifier, which allows you to change how the user is allowed to interact with the calendar.

  • allowResizing
  • allowRescheduling
  • allowEventCreation
  • createEventGesture

The CalendarBody takes a CalendarSnapping ValueNotifier that allows you to customize how snapping works for Day/Multiday views.

  • snapIntervalMinutes
  • snapToTimeIndicator
  • snapToOtherEvents
  • snapRange
  • eventSnapStrategy

Examples:

MultiDayHeaderConfiguration
CalendarHeader(
  multiDayHeaderConfiguration: MultiDayHeaderConfiguration(
    // Whether to show event tiles, useful if you want to display the header but not the tiles.
    showTiles: true,
    // The height of the tiles.
    tileHeight: 24,
    // Multi day event layout.
    generateMultiDayLayoutFrame: defaultMultiDayFrameGenerator,
    // Maximum number of vertical events.
    maximumNumberOfVerticalEvents: null,
    // The padding used around events.
    eventPadding: EdgeInserts.zero,
    // The configuration for triggering page navigation.
    pageTriggerConfiguration: PageTriggerConfiguration(),
    // The configuration for triggering scroll navigation.
    scrollTriggerConfiguration: ScrollTriggerConfiguration(),
  ),
  interaction: ValueNotifier(
    CalendarInteraction(
      // Allow events to be resized.
      allowResizing: true,
      // Allow events to be rescheduled.
      allowRescheduling: true,
      // Allow events to be created.
      allowEventCreation: true,
    ),
  ),
);
MultiDayBodyConfiguration
CalendarBody(
  multiDayBodyConfiguration: MultiDayBodyConfiguration(
    // Whether to show events that are longer than 1 day.
    showMultiDayEvents: true,
    // The padding between events and the edge of the day.
    horizontalPadding: EdgeInsets.only(left: 0, right: 4),
    // The minimum height of a tile.
    minimumTileHeight: 24.0,
    // The configuration for triggering page navigation.
    pageTriggerConfiguration: PageTriggerConfiguration(),
    // The configuration for triggering scroll navigation.
    scrollTriggerConfiguration: ScrollTriggerConfiguration(),
    // The layout strategy used by the body to layout events.
    eventLayoutStrategy: overlapLayoutStrategy,
    // The physics used by the scrollable body.
    scrollPhysics: BouncingScrollPhysics(),
    // The physics used by the page view.
    pageScrollPhysics: BouncingScrollPhysics(),
  ),
  interaction: ValueNotifier(
    CalendarInteraction(
      // Allow events to be resized.
      allowResizing: true,
      // Allow events to be rescheduled.
      allowRescheduling: true,
      // Allow events to be created.
      allowEventCreation: true,
    ),
  ),
  snapping: ValueNotifier(
    CalendarSnapping(
      snapIntervalMinutes: 15,
      // Whether to snap to the time indicator.
      snapToTimeIndicator: true,
      // Whether to snap to other events.
      snapToOtherEvents: true,
      // The range in which events will be snapped,
      // ex. 15 minutes: A event will snap to other events that are within 15 minutes from it.
      snapRange: Duration(minutes: 5),
      // The strategy used to snap events to specific intervals.
      eventSnapStrategy: defaultSnapStrategy,
    ),
  ),
);
MonthBodyConfiguration
CalendarBody(
  monthBodyConfiguration: MonthBodyConfiguration(
    // Whether to show event tiles, useful if you want to display the header but not the tiles.
    showTiles: true,
    // The height of the tiles.
    tileHeight: 24,
    // Multi day frame generator.
    generateMultiDayLayoutFrame: defaultMultiDayFrameGenerator,
    // The padding used around events.
    eventPadding: EdgeInserts.zero,
    // The layout strategy used to layout events.
    eventLayoutStrategy: defaultMultiDayLayoutStrategy,
    // The configuration for triggering page navigation.
    pageTriggerConfiguration: PageTriggerConfiguration(),
    // The configuration for triggering scroll navigation.
    scrollTriggerConfiguration: ScrollTriggerConfiguration(),
  ),
  interaction: ValueNotifier(
    CalendarInteraction(
      // Allow events to be resized.
      allowResizing: true,
      // Allow events to be rescheduled.
      allowRescheduling: true,
      // Allow events to be created.
      allowEventCreation: true,
    ),
  ),
);
ScheduleViewConfiguration
CalendarBody(
  scheduleBodyConfiguration: ScheduleBodyConfiguration(
    // Behavior of empty days in the schedule body.
    emptyDay: EmptyDayBehavior.hide,
    // The configuration for triggering page navigation.
    pageTriggerConfiguration: PageTriggerConfiguration(),
    // The configuration for triggering scroll navigation.
    scrollTriggerConfiguration: ScrollTriggerConfiguration(),
    // The physics used by the scrollable body.
    scrollPhysics: BouncingScrollPhysics(),
    // The physics used by the page view. (for paginated views)
    pageScrollPhysics: BouncingScrollPhysics(),
  ),
  interaction: ValueNotifier(
    CalendarInteraction(
      // Allow events to be resized.
      allowResizing: true,
      // Allow events to be rescheduled.
      allowRescheduling: true,
      // Allow events to be created.
      allowEventCreation: true,
    ),
  ),
);

Zoom Example

It is possible to zoom the calendar in/out. The demo example shows how this can be accomplished with the CalendarZoomDetector

Customizing the look

There are a few ways to customize the look of the calendar:

General Components:

Tile Components

The TileComponents object is used to customize the look of the tiles displayed in the calendar. The CalendarBody and CalendarHeader have a TileComponents object that can be customized.

TileComponents details
TileComponents(
  // The default builder for stationary event tiles.
  tileBuilder: (event) => Container(),

  // The builder for the stationary event tile. (When it is being dragged)
  tileWhenDraggingBuilder: (event) => Container(),

  // The builder for the feedback tile, follows the cursor/finger. (When it is being dragged)
  feedbackTileBuilder: (event, dropTargetWidgetSize) => Container(),

  // The builder for the drop target event tile.
  dropTargetTile: (event) => Container(),

  // The dragAnchorStrategy used by the [feedbackTileBuilder].
  dragAnchorStrategy: childDragAnchorStrategy,

  // A widget that allows you to customize where the resize handles are placed vertically.
  // Your widget should extend the `ResizeHandlePositionerWidget`
  verticalHandlePositioner: (startResizeHandle, endResizeHandle, showStart, showEnd) => ResizeHandlePositionerWidget() , 

  // The vertical resize handle.
  verticalResizeHandle: Container(),

  // A widget that allows you to customize where the resize handles are placed horizontally.
  // Your widget should extend the `ResizeHandlePositionerWidget`
  horizontalHandlePositioner: (startResizeHandle, endResizeHandle, showStart, showEnd) => ResizeHandlePositionerWidget() ,

  // The horizontal resize handle.
  horizontalResizeHandle: Container(),
)
ScheduleTileComponents details
ScheduleTileComponents(
  // The default builder for stationary event tiles.
  tileBuilder: (event) => Container(),

  // The builder for the stationary event tile. (When it is being dragged)
  tileWhenDraggingBuilder: (event) => Container(),

  // The builder for the feedback tile, follows the cursor/finger. (When it is being dragged)
  feedbackTileBuilder: (event, dropTargetWidgetSize) => Container(),

  // The builder for the drop target event tile.
  dropTargetTile: (event) => Container(),

  // The dragAnchorStrategy used by the [feedbackTileBuilder].
  dragAnchorStrategy: childDragAnchorStrategy,

  // The builder for empty items.
  emptyItemBuilder: (tileRange) => Container(),

  // The builder for month items.
  monthItemBuilder: (monthRange) => Container(),
)

General Components

The CalendarView takes a components object.

For every type of ViewConfiguration there are Header and Body components which can be customized.

By default the calendar uses default components which can be customized with ComponentStyles, you have the option to override these components by supplying a builder to the Components object.

Default Component Styles

You can style the default components by passing the CalendarView CalendarComponents object that contains a MonthComponentStyles and/or MultiDayComponentStyles and/or

ScheduleComponents

Custom Components

MonthComponents
CalendarView(
  components: CalendarComponents(
    monthComponents: MonthComponents(
      headerComponents: MonthHeaderComponents(
        // Custom day header builder.
        weekDayHeaderBuilder: (date, style) => SizedBox(),
      ),
      bodyComponents: MonthBodyComponents(
        // Custom day header for month cells.
        monthDayHeaderBuilder(date, style) => SizedBox(),

        // Custom grid builder.
        monthGridBuilder: (style) => SizedBox(),

        // Custom left trigger. (Must constrain the width)
        leftTriggerBuilder: (pageWidth) => SizedBox(),

        // Custom right trigger. (Must constrain the width)
        rightTriggerBuilder: (pageWidth) => SizedBox(),

        // Custom overlay builders.
        overlayBuilders: OverlayBuilders(
          // multiDayOverlayBuilder: , 
          // multiDayOverlayPortalBuilder: , 
          multiDayPortalOverlayButtonBuilder:(portalController, numberOfHiddenRows, style) => SizedBox(),
        ),
      ),
    ),
  ),
);
MultiDayComponents
CalendarView(
  components: CalendarComponents(
    multiDayComponents: MultiDayComponents(
      headerComponents: MultiDayHeaderComponents(
        // Custom Day Header builder.
        dayHeaderBuilder: (date, style) => CustomWidget(),

        // Custom Week Number builder.
        weekNumberBuilder: (visibleDateTimeRange, style) => CustomWidget(),

        // Custom left trigger. (Must constrain the width)
        leftTriggerBuilder: (pageWidth) => SizedBox(width: pageWidth / 20),

        // Custom right trigger. (Must constrain the width)
        rightTriggerBuilder: (pageWidth) => SizedBox(width: pageWidth / 20),

        // Custom overlay builders.
        overlayBuilders: OverlayBuilders(
          // multiDayOverlayBuilder: , 
          // multiDayOverlayPortalBuilder: , 
          multiDayPortalOverlayButtonBuilder:(portalController, numberOfHiddenRows, style) => SizedBox(),
        ),
      ),
      bodyComponents: MultiDayBodyComponents(
        // Custom Hour Line builder.
        hourLines: (heightPerMinute, timeOfDayRange, style) => CustomWidget(),

        // Custom time line builder.
        timeline: (heightPerMinute, timeOfDayRange, style) => CustomWidget(),

        // Custom day separator builder.
        daySeparator: (style) => CustomWidget(),

        // Custom event indicator builder.
        timeIndicator: (timeOfDayRange, heightPerMinute, timelineWidth, style) => CustomWidget(),

        // Left trigger. (Must constrain the width)
        leftTriggerBuilder: (pageHeight) => SizedBox(width: pageHeight / 20),

        // Right trigger. (Must constrain the width)
        rightTriggerBuilder: (pageHeight) => SizedBox(width: pageHeight / 20),

        // Top trigger. (Must constrain the height)
        topTriggerBuilder: (viewPortHeight) => SizedBox(height: viewPortHeight / 20),

        // Bottom trigger. (Must constrain the height)
        bottomTriggerBuilder: (viewPortHeight) => SizedBox(height: viewPortHeight / 20),
      ),
    ),
  ),
);
ScheduleComponents
CalendarView(
  components: CalendarComponents(
    scheduleComponents: ScheduleComponents(
      // Custom leading date builder.
      dayHeaderBuilder: (date, style) => Container(),
      // Custom tile highlight builder.
      scheduleTileHighlightBuilder: (date, dateTimeRange, style, child) => Container(child: child),
    ),
  ),
);

Event layout

Vertical layout

The packages makes use of CustomMultiChildLayout to layout event tiles. The CustomMultiChildLayout uses a MultiChildLayoutDelegate to determine the positions of tiles.

The package provides some default layoutStrategies, overlapLayoutStrategy and sideBySideLayoutStrategy for day/multi-day views. You can create your own layoutStrategy, using the two provided strategies as a reference might be useful.

Horizontal layout

For horizontal event layouts (such as in the month view or multiday header), the package arranges event tiles in rows, where each row represents events and each column represents a day. Events that span multiple days are displayed as a single tile stretching across the relevant columns.

The layout is managed using a custom MultiChildLayoutDelegate, which relies on a layout frame (MultiDayLayoutFrame) to determine the position and size of each event tile. The layout frame is generated by a strategy function (such as defaultMultiDayFrameGenerator) and contains all the metadata needed for rendering:

  • The row and column assignments for each event
  • The total number of rows required
  • A mapping of columns (dates) to the number of rows
  • The list of events to display

The layout frame ensures that:

  • Overlapping events are placed on separate rows.
  • Multi-day events are visually continuous across the days they span.
  • The number of rows adapts to the number of overlapping or multi-day events.

By default, the package provides a layout strategy that:

  • Assigns each event to the first available row where it does not overlap with other events.
  • Calculates the width of each event tile based on the number of days it spans.
  • Updates the layout frame to reflect the current visible date range and event set.