Appearance topic

Appearance

This is part of the kalender documentation.

What the calendar looks like: event tiles, theming, and replacing the default components. For where tiles are placed rather than how they look, see Layout.

Tile Components

TileComponents is the primary way to control how events look in the calendar. Pass it to KalenderHeader and/or KalenderBody for day, multi-day, and month views.

For schedule views, use ScheduleTileComponents instead (passed via KalenderBody.scheduleTileComponents).

Simple tile

For most apps a plain tileBuilder is all you need:

KalenderBody(
  multiDayTileComponents: TileComponents(
    tileBuilder: (context, event, tileRange) {
      final myEvent = event as Event;
      return Container(
        decoration: BoxDecoration(
          color: myEvent.color ?? Colors.blue,
          borderRadius: BorderRadius.circular(4),
        ),
        padding: const EdgeInsets.all(4),
        child: Text(myEvent.title, style: const TextStyle(color: Colors.white)),
      );
    },
  ),
)

All TileComponents options

Every aspect of an event tile's appearance and drag behavior can be overridden. Only tileBuilder is required. Every other field defaults to null, which keeps the package's own behavior, so set only what you want to change.

TileComponents reference
TileComponents(
  // Required: the stationary event tile.
  tileBuilder: (context, event, tileRange) => Container(),

  // Shown over the calendar in portal overlays instead of tileBuilder.
  overlayTileBuilder: (context, event, tileRange) => Container(),

  // Shown in place of the tile while it is being dragged.
  tileWhenDraggingBuilder: (context, event) => Container(),

  // The tile that follows the cursor / finger during a drag.
  feedbackTileBuilder: (context, event, dropTargetWidgetSize) => Container(),

  // Rendered beneath the dragged tile to show where it will land.
  dropTargetTile: (context, event) => Container(),

  // The drag anchor strategy used by feedbackTileBuilder.
  dragAnchorStrategy: childDragAnchorStrategy,

  // Position and size the resize handles. `details` carries the tile's geometry
  // and builds the detectors, so decide the layout and place them.
  resizeHandlePositioner: (context, details) => Stack(
    fit: StackFit.expand,
    children: [
      if (details.showStart())
        Positioned(top: 0, left: 0, right: 0, height: 8, child: details.startResizeDetector),
      if (details.showEnd())
        Positioned(bottom: 0, left: 0, right: 0, height: 8, child: details.endResizeDetector),
    ],
  ),

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

  // The horizontal resize handle widget.
  horizontalResizeHandle: Container(),
)

Warning

The snippet above omits resizeDragAnchorStrategy. It defaults to a pointer anchor. Setting it to childDragAnchorStrategy makes a vertical resize jump to the neighbouring day on the smallest sideways movement.

ScheduleTileComponents

Schedule view tiles have a different set of builders since they are laid out in a list rather than a grid.

ScheduleTileComponents reference
ScheduleTileComponents(
  // Required: the stationary event tile.
  tileBuilder: (context, event, tileRange) => Container(),

  // Shown in place of the tile while it is being dragged.
  tileWhenDraggingBuilder: (context, event) => Container(),

  // The tile that follows the cursor / finger during a drag.
  feedbackTileBuilder: (context, event, dropTargetWidgetSize) => Container(),

  // The drag anchor strategy used by feedbackTileBuilder.
  dragAnchorStrategy: childDragAnchorStrategy,
)

Schedule tiles cannot be resized, so ScheduleTileComponents takes no resize handles. It also takes no dropTargetTile: during a drag the schedule marks the destination by highlighting the row, built by ScheduleComponents.scheduleTileHighlightBuilder and styled by ScheduleTileHighlightStyle. The empty-day and month heading rows are list rows rather than event tiles, so their builders live on ScheduleComponents as well.

Advanced tiles with event-tile utilities

For tiles that need to know the exact tapped time or find nearby events, use the provided mixins.

Tip

Disabling the calendar's built-in tap detector: The calendar only wraps event tiles in a GestureDetector when onEventTapped or onEventTappedWithDetail is provided in KalenderCallbacks. If you omit both callbacks, the wrapper is skipped and a GestureDetector inside your custom tile widget can receive events unobstructed. This is the intended pattern when using DayEventTileUtils or MultiDayEventTileUtils.

DayEventTileUtils (day / multi-day body tiles)
class CustomDayEventTile extends StatelessWidget with DayEventTileUtils {
  @override
  final KalenderEvent event;

  @override
  final KalenderDateTimeRange tileRange;

  const CustomDayEventTile({
    super.key,
    required this.event,
    required this.tileRange,
  });

  Event get myEvent => event as Event;

  @override
  Widget build(BuildContext context) {
    return GestureDetector(
      onTapUp: (details) {
        // Convert a local tap position into an exact DateTime.
        final tappedTime = dateTimeFromPosition(context, details.localPosition);
        debugPrint('Tapped at: $tappedTime');

        // Find events that overlap a ±15-minute window around this one.
        final nearby = nearbyEvents(
          context,
          before: const Duration(minutes: 15),
          after: const Duration(minutes: 15),
        );
        debugPrint('Found ${nearby.length} nearby events');
      },
      child: Container(
        decoration: BoxDecoration(
          color: myEvent.color ?? Colors.blue,
          borderRadius: BorderRadius.circular(4),
        ),
        padding: const EdgeInsets.all(4),
        child: Text(myEvent.title, style: const TextStyle(color: Colors.white)),
      ),
    );
  }

  // Static factory. Pass directly to TileComponents.tileBuilder.
  static Widget builder(BuildContext context, KalenderEvent event, KalenderDateTimeRange tileRange) =>
      CustomDayEventTile(
        event: event,
        tileRange: tileRange,
      );
}

const dayTileComponents = TileComponents(tileBuilder: CustomDayEventTile.builder);
MultiDayEventTileUtils (month view / multi-day header tiles)
class CustomMultiDayEventTile extends StatelessWidget with MultiDayEventTileUtils {
  @override
  final KalenderEvent event;

  @override
  final KalenderDateTimeRange tileRange;

  const CustomMultiDayEventTile({
    super.key,
    required this.event,
    required this.tileRange,
  });

  Event get myEvent => event as Event;

  @override
  Widget build(BuildContext context) {
    return GestureDetector(
      onTapUp: (details) {
        // Convert a horizontal tap position into a specific date.
        final tappedDate = dateFromPosition(context, details.localPosition);
        debugPrint('Tapped on: $tappedDate');

        final overlapping = nearbyEvents(context);
        debugPrint('Found ${overlapping.length} overlapping events');
      },
      child: Container(
        decoration: BoxDecoration(
          color: myEvent.color ?? Colors.green,
          borderRadius: BorderRadius.circular(4),
        ),
        padding: const EdgeInsets.symmetric(horizontal: 4, vertical: 2),
        child: Text(
          myEvent.title,
          style: const TextStyle(color: Colors.white, fontSize: 12),
          overflow: TextOverflow.ellipsis,
        ),
      ),
    );
  }

  static Widget builder(BuildContext context, KalenderEvent event, KalenderDateTimeRange tileRange) =>
      CustomMultiDayEventTile(
        event: event,
        tileRange: tileRange,
      );
}

const multiDayTileComponents = TileComponents(tileBuilder: CustomMultiDayEventTile.builder);

Theming

By default the calendar follows your app's Material 3 theme: line colors, text styles, and the rest are derived from the ambient ColorScheme and TextTheme.

To change how every calendar in the app looks, register a KalenderThemeData on your theme. Any field you leave out keeps its Material 3 default.

MaterialApp(
  theme: ThemeData(
    colorScheme: ColorScheme.fromSeed(seedColor: Colors.blue),
    extensions: [
      KalenderThemeData(
        hourLinesStyle: HourLinesStyle(thickness: 2),
        timeIndicatorStyle: TimeIndicatorStyle(lineColor: Colors.pink),
      ),
    ],
  ),
)

Theming part of the app

Registering on ThemeData covers every calendar in the app. To theme one of them differently, wrap it in a KalenderTheme:

KalenderTheme(
  data: const KalenderThemeData(
    hourLinesStyle: HourLinesStyle(thickness: 2),
  ),
  child: KalenderView(
    eventsController: DefaultEventsController(),
    kalenderController: KalenderController(),
    viewConfiguration: MultiDayViewConfiguration.week(),
    body: KalenderBody(),
  ),
)

The nearest one wins when they nest, and fields it leaves out fall through to the theme registered on ThemeData, so a scope can change one thing without restating the rest.

This is an InheritedTheme, so it also reaches widgets the calendar builds into an Overlay, such as the tile that follows a drag.

How a style is resolved

Four layers, most specific first. Each one fills in the fields the layer above it leaves null.

  1. A style passed directly to a widget, which is how a custom builder styles the widget it returns (see Appearance).
  2. The nearest KalenderTheme above the calendar.
  3. The KalenderThemeData registered on ThemeData.extensions.
  4. The Material 3 defaults.

Note

Gutter widths are not styles. The month week number column and the multi-day timeline are drawn in the body and reserved again in the header, so the calendar measures each once and both halves read that number. A KalenderTheme scoped inside one half restyles the gutter there without resizing it. Set the width with MonthBodyComponents.weekNumberWidth or MultiDayBodyComponents.timelineWidth.

Switching themes transitions the calendar's colors along with the rest of the app. A KalenderTheme scope does not animate.

The overflow overlay

The overlay that opens from the +3 button, which stands in for events that do not fit, is themed the same way. Its card and close button take Flutter's own CardThemeData and ButtonStyle.

KalenderThemeData(
  multiDayOverlayStyle: MultiDayOverlayStyle(
    cardTheme: CardThemeData(
      color: Colors.white,
      shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(16)),
    ),
    closeButtonStyle: IconButton.styleFrom(backgroundColor: Colors.amber),
    // Dims the calendar behind the card. Transparent by default.
    barrierColor: Colors.black54,
    width: 320,
  ),
)

closeButtonStyle merges over the defaults of a filled tonal icon button, so set only the fields you change.

Appearance / Custom Components

Pass a KalenderComponents object to KalenderView to override the default widget builders.

Note

KalenderComponents carries builders only. Styles live on KalenderThemeData: register one on ThemeData.extensions for the whole app, or wrap a calendar in a KalenderTheme to style one of them.

Every builder receives a BuildContext as its first argument and resolves what it needs from it: styles with KalenderTheme.of(context), and the state of the enclosing calendar with KalenderScope, one accessor per value.

MultiDayComponents
KalenderComponents(
  multiDayComponents: MultiDayComponents(
    headerComponents: MultiDayHeaderComponents(
      dayHeaderBuilder: (context, date) => CustomWidget(),
      weekNumberBuilder: (context, visibleDateTimeRange) => CustomWidget(),
      leftTriggerBuilder: (context, pageWidth) => SizedBox(width: pageWidth / 20),
      rightTriggerBuilder: (context, pageWidth) => SizedBox(width: pageWidth / 20),
      overlayBuilders: OverlayBuilders(
        multiDayPortalOverlayButtonBuilder:
            (context, portalController, numberOfHiddenRows) => SizedBox(),
      ),
    ),
    bodyComponents: MultiDayBodyComponents(
      hourLines: (context, heightPerMinute, timeOfDayRange) => CustomWidget(),
      timeline: (context, heightPerMinute, timeOfDayRange, eventBeingDragged, visibleDateTimeRange) =>
          CustomWidget(),
      // Sizes the timeline gutter, for example to fit a custom timeline's labels.
      timelineWidth: (context, timeOfDayRange) => 48,
      daySeparator: (context) => CustomWidget(),
      timeIndicator: (context, timeOfDayRange, heightPerMinute, location) => CustomWidget(),
      leftTriggerBuilder: (context, pageWidth) => SizedBox(width: pageWidth / 20),
      rightTriggerBuilder: (context, pageWidth) => SizedBox(width: pageWidth / 20),
      topTriggerBuilder: (context, viewPortHeight) => SizedBox(height: viewPortHeight / 20),
      bottomTriggerBuilder: (context, viewPortHeight) => SizedBox(height: viewPortHeight / 20),
    ),
  ),
)
MonthComponents
KalenderComponents(
  monthComponents: MonthComponents(
    headerComponents: MonthHeaderComponents(
      weekDayHeaderBuilder: (context, date) => SizedBox(),
    ),
    bodyComponents: MonthBodyComponents(
      monthDayHeaderBuilder: (context, date) => SizedBox(),
      // Custom per-cell background, or use the ready-made
      // MonthDayCell.shadeAdjacentMonths() to shade adjacent-month days.
      monthDayCellBuilder: (context, details) => SizedBox(),
      monthGridBuilder: (context, numberOfRows) => SizedBox(),
      weekNumberBuilder: (context, visibleDateTimeRange) => SizedBox(),
      leftTriggerBuilder: (context, pageWidth) => SizedBox(),
      rightTriggerBuilder: (context, pageWidth) => SizedBox(),
      overlayBuilders: OverlayBuilders(
        multiDayPortalOverlayButtonBuilder:
            (context, portalController, numberOfHiddenRows) => SizedBox(),
      ),
    ),
  ),
)
ScheduleComponents
KalenderComponents(
  scheduleComponents: ScheduleComponents(
    // The date column shown beside the first row of each day.
    leadingDateBuilder: (context, date) => Container(),

    // Wraps a row to highlight it as the drop target during a drag.
    scheduleTileHighlightBuilder: (context, date, range, child) =>
        Container(child: child),

    // Optional: builder for days with no events.
    emptyItemBuilder: (context, tileRange) => Container(),

    // Optional: builder for the month heading rows.
    monthItemBuilder: (context, monthRange) => Container(),
  ),
)

Classes

DayHeader Appearance
A widget that displays the name of the day and the day number of the week.
DayHeaderStyle Appearance
The styling class for the DayHeader.
DayNumberStyle Appearance
The DayNumberStyle class is used by the DayNumber widget.
DaySeparator Appearance
A widget that displays a separator between days.
DaySeparatorStyle Appearance
The style for the DaySeparator widget.
HourLines Appearance
A widget that displays lines for each hour based on the timeOfDayRange and heightPerMinute.
HourLinesStyle Appearance
The style of the HourLines widget.
KalenderComponents Appearance
A class holding the widget builders used by the KalenderView.
KalenderTheme Appearance
Applies a KalenderThemeData to the calendars below it.
KalenderThemeData Appearance
The calendar's visual theme, following the same layering as Flutter's own component themes.
MonthBodyComponents Appearance
The component builders used by the MonthBody.
MonthComponents Appearance
A class containing custom widget builders for the MonthBody and MonthHeader.
MonthDayCell Appearance
Renders the background of a single day cell in the month body.
MonthDayCellDetails Appearance
Details describing a single day cell, passed to a MonthDayCellBuilder.
MonthDayHeader Appearance
A widget that displays the day number.
MonthDayHeaderStyle Appearance
The style of the MonthDayHeader.
MonthGrid Appearance
A widget that displays the month grid.
MonthGridStyle Appearance
The MonthGridStyle class is used by the MonthGrid widget.
MonthHeaderComponents Appearance
The component builders used by the MonthHeader.
MultiDayBodyComponents Appearance
The component builders used by the MultiDayBody.
MultiDayComponents Appearance
A class containing custom widget builders for the MultiDayBody and MultiDayHeader.
MultiDayEventOverlayTile Appearance
MultiDayHeaderComponents Appearance
The component builders used by the MultiDayHeader.
MultiDayOverlay Appearance
MultiDayOverlayPortal Appearance
A widget that manages the overlay portal for a single day.
MultiDayOverlayStyle Appearance
MultiDayPortalOverlayButton Appearance
MultiDayPortalOverlayButtonStyle Appearance
OverlayBuilders Appearance
Builders used to create the overlayPortal, overlay and overlay button widgets.
ResizeHandleStyle Appearance
The style of the resize handles laid out by DefaultResizeHandles.
ScheduleComponents Appearance
A class containing custom widget builders for the ScheduleBody.
ScheduleDate Appearance
A widget that displays the name of the day and the day number of the week.
ScheduleDateStyle Appearance
The style of the ScheduleDate.
ScheduleTileComponents Appearance
The components used by the ScheduleBody to render the event tiles.
ScheduleTileHighlight Appearance
A widget that highlights the list item if the date is within the given range.
ScheduleTileHighlightStyle Appearance
TileComponents Appearance
The components used by the MultiDayBody/MonthBody to render the event tiles.
TimeIndicator Appearance
A widget that displays the current time as a line and a circle.
TimeIndicatorStyle Appearance
The style of the TimeIndicator widget.
TimeLine Appearance
A widget that displays a list of times based on the timeOfDayRange and heightPerMinute.
TimelineStyle Appearance
The style of the TimeLine widget.
WeekDayHeader Appearance
A widget that displays the name of the day of the week.
WeekDayHeaderStyle Appearance
The WeekDayHeaderStyle class is used by the default WeekDayHeader widget.
WeekNumber Appearance
A widget that displays the week number.
WeekNumberStyle Appearance
The style of the WeekNumber.

Mixins

DayEventTileUtils Appearance
A mixin that provides useful utilities for day-based event tiles.
EventTileUtils Appearance
MultiDayEventTileUtils Appearance
A mixin that provides useful utilities for multi-day event tiles.
TimeLineUtils Appearance
A mixin that provides utility methods for the TimeLine and HourLines widget.

Extensions

KalenderLocale on BuildContext Appearance
Gives a string builder access to the locale of the calendar it is building for.

Constants

kDefaultWeekNumberWidth → const double Appearance
The width defaultWeekNumberWidth returns when the style sets none.

Typedefs

DateStringBuilder = String Function(BuildContext context, DateTime date) Appearance
Builds the text displayed for date.
DayHeaderBuilder = Widget Function(BuildContext context, DateTime date) Appearance
The day header builder.
DaySeparatorBuilder = Widget Function(BuildContext context) Appearance
The day separator builder.
EmptyItemBuilder = Widget Function(BuildContext context, KalenderDateTimeRange tileRange) Appearance
The builder for the empty item.
FeedbackTileBuilder = Widget Function(BuildContext context, KalenderEvent event, Size dropTargetWidgetSize) Appearance
The builder for the feedback tile. (When dragging)
HiddenEventCountStringBuilder = String Function(BuildContext context, int numberOfHiddenEvents) Appearance
Builds the text displayed on the overlay button that opens the hidden events.
HourLinesBuilder = Widget Function(BuildContext context, double heightPerMinute, KalenderTimeRange timeOfDayRange) Appearance
The hour lines builder.
KalenderTimeStringBuilder = String Function(BuildContext context, KalenderTime time) Appearance
Builds the text displayed for time.
MonthDayCellBuilder = Widget Function(BuildContext context, MonthDayCellDetails details) Appearance
Builds the background of a single day cell in the month body.
MonthDayHeaderBuilder = Widget Function(BuildContext context, DateTime date) Appearance
The month day header builder.
MonthGridBuilder = Widget Function(BuildContext context, int numberOfRows) Appearance
The month grid builder.
MonthItemBuilder = Widget Function(BuildContext context, KalenderDateTimeRange tileRange) Appearance
The builder for the month item.
MultiDayOverlayBuilder = Widget Function(BuildContext context, {required DateTime date, required List<KalenderEvent> events, required RenderBoxCallback getMultiDayEventLayoutRenderBox, required RenderBoxCallback getOverlayPortalRenderBox, required MultiDayOverlayEventTileBuilder overlayTileBuilder, required OverlayPortalController portalController, required double tileHeight}) Appearance
A function that returns a MultiDayOverlay widget.
MultiDayOverlayEventTileBuilder = MultiDayEventOverlayTile Function(BuildContext context, KalenderEvent event, FloatingDateTimeRange floatingRange, VoidCallback dismissOverlay) Appearance
A function that returns a MultiDayEventOverlayTile for the multi-day overlay.
MultiDayOverlayPortalBuilder = Widget Function(BuildContext context, {required DateTime date, required List<KalenderEvent> events, required RenderBoxCallback getMultiDayEventLayoutRenderBox, required int numberOfHiddenRows, required OverlayBuilders? overlayBuilders, required MultiDayOverlayEventTileBuilder overlayTileBuilder, required double tileHeight}) Appearance
A function that returns a MultiDayOverlayPortal.
MultiDayPortalOverlayButtonBuilder = Widget Function(BuildContext context, OverlayPortalController portalController, int numberOfHiddenRows) Appearance
The builder used to create the button for the MultiDayPortalOverlayButton.
ScheduleDateBuilder = Widget Function(BuildContext context, FloatingDateTime date) Appearance
The day header builder.
ScheduleTileHighlightBuilder = Widget Function(BuildContext context, FloatingDateTime date, ValueNotifier<FloatingDateTimeRange?> range, Widget child) Appearance
The schedule tile highlight builder.
TileBuilder = Widget Function(BuildContext context, KalenderEvent event, KalenderDateTimeRange tileRange) Appearance
The default builder for the event tiles.
TileDropTargetBuilder = Widget Function(BuildContext context, KalenderEvent event) Appearance
The builder for the drop target event tile.
TileWhenDraggingBuilder = Widget Function(BuildContext context, KalenderEvent event) Appearance
The builder for the event tile when dragging.
TimeIndicatorBuilder = Widget Function(BuildContext context, KalenderTimeRange timeOfDayRange, double heightPerMinute, Location? location) Appearance
The time indicator builder.
TimeLineBuilder = Widget Function(BuildContext context, double heightPerMinute, KalenderTimeRange timeOfDayRange, ValueNotifier<KalenderEvent?> eventBeingDragged, ValueNotifier<KalenderDateTimeRange?> visibleDateTimeRange) Appearance
The time line builder.
TimelineWidthBuilder = double Function(BuildContext context, KalenderTimeRange timeOfDayRange) Appearance
Resolves the width of the timeline gutter.
WeekDayHeaderBuilder = Widget Function(BuildContext context, DateTime date) Appearance
The week day header builder.
WeekNumberBuilder = Widget Function(BuildContext context, KalenderDateTimeRange visibleDateTimeRange) Appearance
The week number builder.
WeekNumberWidthBuilder = double Function(BuildContext context) Appearance
The width of the month's week number column.