image

06 Jan 2026

9K

35K

Building a Calendar Widget with Event Highlights in Flutter

Modern mobile applications often require sophisticated UI components to enhance user experience. A highly requested feature, especially for productivity, scheduling, and event-driven apps, is a calendar widget capable of displaying and highlighting events. Flutter, with its powerful UI toolkit, provides an excellent platform for building such custom widgets. This article will guide you through creating a dynamic calendar widget with event highlighting capabilities in Flutter, leveraging the popular table_calendar package for efficiency.

Why a Custom Calendar with Event Highlighting?

While basic date pickers are built-in, a full-fledged calendar with custom event markers offers several advantages:

  • Enhanced User Experience: Users can quickly identify important dates at a glance.
  • Contextual Information: Provides immediate visual cues for upcoming meetings, tasks, or special occasions.
  • Branding and Aesthetics: Allows for complete control over the calendar's look and feel, aligning it with the app's overall design language.
  • Interactivity: Supports seamless navigation, date selection, and event detail display.

Prerequisites

To follow along, you should have a basic understanding of Flutter development and Dart programming. We'll be using the table_calendar package, so ensure your Flutter environment is set up.

Setting Up Your Project

First, create a new Flutter project or open an existing one. Then, add the table_calendar package to your pubspec.yaml file:


dependencies:
  flutter:
    sdk: flutter
  table_calendar: ^3.0.0 # Use the latest stable version

Run flutter pub get to fetch the new dependency.

Defining the Event Model

To highlight events, we first need a way to represent them. A simple data model for an event might include a title and a date. You can extend this with additional properties like description, time, or color.


class Event {
  final String title;
  final DateTime date; // Optional: Could store a full DateTime if time is relevant

  const Event(this.title, this.date);

  @override
  String toString() => title;

  // For `table_calendar` event comparison (optional, but good practice)
  @override
  bool operator ==(Object other) =>
      identical(this, other) ||
      other is Event &&
          runtimeType == other.runtimeType &&
          title == other.title &&
          date.year == other.date.year &&
          date.month == other.date.month &&
          date.day == other.date.day;

  @override
  int get hashCode => title.hashCode ^ date.year.hashCode ^ date.month.hashCode ^ date.day.hashCode;
}

Implementing the Calendar Widget

Now, let's integrate table_calendar and add the event highlighting logic. We'll create a stateful widget to manage the selected date and displayed events.

1. Initialize Calendar Controller and Event Map

Inside your StatefulWidget's `_State` class, you'll need to initialize a `TableCalendar` controller and a map to store your events, typically grouped by date.


import 'package:flutter/material.dart';
import 'package:table_calendar/table_calendar.dart';
import 'package:intl/intl.dart'; // For date formatting

// Your Event class definition goes here (as defined above)
class Event {
  final String title;
  final DateTime date;

  const Event(this.title, this.date);

  @override
  String toString() => title;

  @override
  bool operator ==(Object other) =>
      identical(this, other) ||
      other is Event &&
          runtimeType == other.runtimeType &&
          title == other.title &&
          date.year == other.date.year &&
          date.month == other.date.month &&
          date.day == other.date.day;

  @override
  int get hashCode => title.hashCode ^ date.year.hashCode ^ date.month.hashCode ^ date.day.hashCode;
}

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

  @override
  State createState() => _CalendarWithEventsState();
}

class _CalendarWithEventsState extends State {
  late final ValueNotifier<List<Event>> _selectedEvents;
  CalendarFormat _calendarFormat = CalendarFormat.month;
  RangeSelectionMode _rangeSelectionMode = RangeSelectionMode.disabled;
  DateTime _focusedDay = DateTime.now();
  DateTime? _selectedDay;

  // A sample map of events for demonstration. In a real app, this would come from a backend or local storage.
  final Map<DateTime, List<Event>> _events = {
    DateTime.utc(2023, 10, 20): [const Event('Meeting', DateTime.utc(2023, 10, 20))],
    DateTime.utc(2023, 10, 21): [
      const Event('Team Lunch', DateTime.utc(2023, 10, 21)),
      const Event('Project Deadline', DateTime.utc(2023, 10, 21)),
    ],
    DateTime.utc(2023, 10, 25): [const Event('Conference', DateTime.utc(2023, 10, 25))],
    DateTime.utc(2023, 11, 5): [const Event('Holiday', DateTime.utc(2023, 11, 5))],
    DateTime.utc(2023, 11, 10): [
      const Event('Client Presentation', DateTime.utc(2023, 11, 10)),
      const Event('Travel Day', DateTime.utc(2023, 11, 10)),
    ],
  };

  @override
  void initState() {
    super.initState();
    _selectedDay = _focusedDay;
    _selectedEvents = ValueNotifier(_getEventsForDay(_selectedDay!));
  }

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

  List<Event> _getEventsForDay(DateTime day) {
    // Normalize the date to UTC without time for consistent mapping
    final normalizedDay = DateTime.utc(day.year, day.month, day.day);
    return _events[normalizedDay] ?? [];
  }

  void _onDaySelected(DateTime selectedDay, DateTime focusedDay) {
    if (!isSameDay(_selectedDay, selectedDay)) {
      setState(() {
        _selectedDay = selectedDay;
        _focusedDay = focusedDay;
        _rangeSelectionMode = RangeSelectionMode.disabled;
      });
      _selectedEvents.value = _getEventsForDay(selectedDay);
    }
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: const Text('Event Calendar'),
      ),
      body: Column(
        children: [
          TableCalendar<Event>(
            firstDay: DateTime.utc(2020, 1, 1),
            lastDay: DateTime.utc(2030, 12, 31),
            focusedDay: _focusedDay,
            selectedDayPredicate: (day) => isSameDay(_selectedDay, day),
            rangeStartDay: null, // No range selection for this example
            rangeEndDay: null,   // No range selection for this example
            calendarFormat: _calendarFormat,
            rangeSelectionMode: _rangeSelectionMode,
            eventLoader: _getEventsForDay, // Crucial for highlighting events
            startingDayOfWeek: StartingDayOfWeek.monday,
            calendarStyle: CalendarStyle(
              outsideDaysVisible: false,
              todayDecoration: BoxDecoration(
                color: Colors.blue.withOpacity(0.5),
                shape: BoxShape.circle,
              ),
              selectedDecoration: const BoxDecoration(
                color: Colors.blue,
                shape: BoxShape.circle,
              ),
              markerDecoration: const BoxDecoration(
                color: Colors.red, // Color of the event highlight dot/marker
                shape: BoxShape.circle,
              ),
            ),
            headerStyle: const HeaderStyle(
              formatButtonVisible: false,
              titleCentered: true,
            ),
            onDaySelected: _onDaySelected,
            onFormatChanged: (format) {
              if (_calendarFormat != format) {
                setState(() {
                  _calendarFormat = format;
                });
              }
            },
            onPageChanged: (focusedDay) {
              _focusedDay = focusedDay;
            },
            // Custom builders for more fine-grained control over UI
            // dayBuilder: (context, day, focusedDay) {
            //   // Example: custom background for specific days
            //   if (day.weekday == DateTime.sunday) {
            //     return Container(
            //       decoration: BoxDecoration(
            //         color: Colors.pink.withOpacity(0.1),
            //         shape: BoxShape.circle,
            //       ),
            //       alignment: Alignment.center,
            //       child: Text(
            //         '${day.day}',
            //         style: const TextStyle(color: Colors.red),
            //       ),
            //     );
            //   }
            //   return null; // Let table_calendar handle other days
            // },
            // eventLoader: defines what events are tied to each day
            // markerBuilder: (context, date, events) {
            //   if (events.isNotEmpty) {
            //     return Positioned(
            //       right: 1,
            //       bottom: 1,
            //       child: _buildEventsMarker(date, events),
            //     );
            //   }
            //   return null;
            // },
          ),
          const SizedBox(height: 8.0),
          Expanded(
            child: ValueListenableBuilder<List<Event>>(
              valueListenable: _selectedEvents,
              builder: (context, value, _) {
                return ListView.builder(
                  itemCount: value.length,
                  itemBuilder: (context, index) {
                    return Container(
                      margin: const EdgeInsets.symmetric(
                        horizontal: 12.0,
                        vertical: 4.0,
                      ),
                      decoration: BoxDecoration(
                        border: Border.all(),
                        borderRadius: BorderRadius.circular(12.0),
                      ),
                      child: ListTile(
                        onTap: () => print('${value[index].title} tapped!'),
                        title: Text('${value[index].title} on ${DateFormat('MMM dd, yyyy').format(value[index].date)}'),
                      ),
                    );
                  },
                );
              },
            ),
          ),
        ],
      ),
    );
  }
}

Key Concepts for Event Highlighting:

  1. _events Map: This map holds your event data, where keys are DateTime objects (representing days) and values are lists of Event objects for that day. It's crucial to normalize the DateTime keys (e.g., to UTC and without time components) to ensure consistent matching with dates provided by table_calendar.
  2. _getEventsForDay Function: This function is passed to table_calendar's eventLoader property. For any given day, table_calendar calls this function to determine what events are associated with it. If this function returns a non-empty list, table_calendar will automatically display a default marker (a small dot) on that day.
  3. calendarStyle.markerDecoration: You can customize the appearance of the default event marker using this property within CalendarStyle. In our example, we set it to a red circle.
  4. markerBuilder (Advanced Customization): If the default marker isn't sufficient, table_calendar provides a markerBuilder callback. This allows you to return any widget to represent events on a specific day. You could display multiple dots, event counts, or custom icons.

Displaying Selected Day's Events

Below the calendar, we've added an Expanded widget containing a ValueListenableBuilder. This builder listens to changes in _selectedEvents (a ValueNotifier) and rebuilds the ListView.builder to show the events for the currently selected day. When _onDaySelected is called, it updates _selectedEvents.value, triggering the UI to refresh.

Customization and Styling

table_calendar offers extensive customization options:

  • CalendarStyle: Control the appearance of days, weekends, selected days, today's date, and event markers.
  • HeaderStyle: Customize the header that displays the current month and year, including buttons for changing the format.
  • builders: For ultimate control, you can use various builders (e.g., dayBuilder, markerBuilder, dowBuilder for day of week) to provide entirely custom widgets for different parts of the calendar. This is where you can implement highly unique highlighting patterns, such as badges with event counts or different colored indicators for different event types.

For example, to create a custom event marker that shows the number of events:


// Inside _CalendarWithEventsState class

// ... other code ...

Widget _buildEventsMarker(DateTime date, List<Event> events) {
  return AnimatedContainer(
    duration: const Duration(milliseconds: 300),
    decoration: BoxDecoration(
      shape: BoxShape.circle,
      color: Colors.deepOrange[300],
    ),
    width: 16.0,
    height: 16.0,
    child: Center(
      child: Text(
        '${events.length}',
        style: const TextStyle().copyWith(
          color: Colors.white,
          fontSize: 12.0,
        ),
      ),
    ),
  );
}

// Then, in your TableCalendar widget:
// ...
          TableCalendar<Event>(
            // ... other properties ...
            markerBuilder: (context, date, events) {
              if (events.isNotEmpty) {
                return Positioned(
                  right: 1,
                  bottom: 1,
                  child: _buildEventsMarker(date, events),
                );
              }
              return null;
            },
          ),
// ...

Uncommenting and implementing the markerBuilder in the `TableCalendar` widget will override the default markerDecoration and use your custom widget instead.

Conclusion

Building a robust calendar widget with event highlighting in Flutter is straightforward, especially when utilizing powerful packages like table_calendar. By defining a clear event model, correctly mapping events to dates, and leveraging the package's extensive customization options, you can create a visually appealing and highly functional calendar that significantly enhances user interaction within your application. This foundation can be extended further to include features like event creation, editing, filtering, and integration with backend services.

Related Articles

May 14, 2026

Building a Multi-Event Countdown Timer Widget with Reminders, Notifications, Repeat, and Custom Labels in Flutter

Building a Multi-Event Countdown Timer Widget with Reminders, Notifications, Repeat, and Custom Labels in Flutter Countdown timers are essential in many applic

May 11, 2026

Unleashing Dynamic UIs: Flutter's Animation Prowess

Unleashing Dynamic UIs: Flutter's Animation Prowess for Slide & Scale Effects Flutter's declarative UI framework, combined with its powerful animation capabilit

May 11, 2026

Building a Product Detail Page Widget in Flutter with Related Items, Review Carousel, Promo Badges, and Quick Buy

Building a Product Detail Page Widget in Flutter with Related Items, Review Carousel, Promo Badges, and Quick Buy A well-designed Product Detail Page (PDP) is