Danny 🎠 6 days ago
I LOVE THE NEW DESIGN SO MUCH AAAAAAAAAAH
nick 🤞 6 days ago
the new designs are finally here! still a few rough edges here and there, but we'll smooth them out. if you spot any bugs, tell us <3

Dynamic Event Calendar

Written by nick • 09.06.2025

In this tutorial, you'll learn how to build a dynamic event calendar using HTML, CSS, and JavaScript. We'll start with a basic HTML structure and progressively add the styling and functionality to create a fully interactive calendar that can display events and navigate through months. ✨

Instead of manually editing the HTML of the calendar, you'll simply add all your events into the JavaScript section and the rest will happen automatically.

Danny wrote a tutorial for a static HTML calendar, which requires manual updates each month. In contrast, my tutorial builds a dynamic event calendar with JavaScript that automatically displays the current month and stays up to date without any manual editing. You can find Danny's tutorial here.



1. Let's start!

Create a file named index.html. We'll create the calendar step by step together! Of course, if you already have a website where you want to integrate the calendar, you could skip this step. ☺️

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Dynamic Event Calendar</title>
  </head>
  <body>
    <!-- Our calendar will go here -->
  </body>
</html>

2. Adding the calendar HTML structure

Now, let's add the HTML elements that will serve as containers for our calendar. We'll have a main container, a navigation bar (for month switching and title), and the grid where the days will be displayed. This structure is minimal because JavaScript will handle populating most of the content. You won't have to edit the HTMl manually.

Place this code inside the <body> tags of your index.html file, or wherever you want to add the calendar:

<div id="calendar-container">
  <div id="calendar-nav"></div>
  <div id="calendar"></div>
</div>

We have three main <div> elements here:

  • #calendar-container: This will hold our entire calendar.
  • #calendar-nav: This is where the month name and navigation buttons will appear.
  • #calendar: This is the grid that will display the days of the month.

3. Styling our calendar with CSS

Let's add some CSS to make it visually appealing! ☺️ We'll embed our styles directly within a <style> tag in the <head> section for simplicity in this tutorial. Feel free to add it to a seperate calendar.css, or your style.css directly, though.

/* Calendar Container */
#calendar-container {
    background: #fff;
    border: 1px solid rgba(0,0,0, 0.1);
    width: max-content;
    max-width: 100%;
}

/* Calendar Navigation */
#calendar-nav {
    background: #0e4a5f;
    color: #fff;
    font-weight: bold;
    text-transform: uppercase;
    letter-spacing: 1px;
    display: flex;
    justify-content: space-between;
    align-items: center;
    padding: 5px 10px;
}

#calendar-nav button {
    background: transparent;
    border: 0;
    color: inherit;
    font-size: 8pt;
    cursor: pointer;
}

#calendar-title {
    text-align: center;
    display: block;
    width: 100%;
}

/* Calendar Grid */
#calendar {
    display: grid;
    grid-template-columns: repeat(7, 1fr);
}

/* Weekday Header */
.weekday {
    background: #f0f0f0;
    font-weight: bold;
    padding: 5px;
    text-transform: uppercase;
    border-bottom: 1px solid #ccc;
    text-align: center;
}

/* Day Cells */
.day {
    display: flex;
    justify-content: center;
    align-items: center;
    height: 30px;
    position: relative;
    background: rgba(255, 255, 255, 0.05);
    box-sizing: border-box;
    padding: 0 5px;
    text-align: center;
}

.alt-row {
    background: rgba(0, 0, 0, 0.05);
}

.current-day {
    font-weight: bold;
    background: #fff;
    border: 1px solid #ccc;
    box-shadow: 0 0 8px rgba(0, 0, 0, 0.1);
}

.adjacent-month {
    opacity: 0.4;
}

.number-container {
    display: flex;
    align-items: center;
    gap: 4px;
}

/* Event Icons and Tooltips */
.event-icon {
    cursor: help;
    display: inline-block;
    position: relative;
}

.tooltip {
    display: none;
    position: absolute;
    left: 20px;
    background: #333;
    color: #fff;
    padding: 5px 10px;
    white-space: nowrap;
    border-radius: 3px;
    z-index: 10;
}

.event-icon:hover .tooltip {
    display: block;
}

This CSS code covers styling for:

  • - The main calendar container, adding a background and shadow.
  • - The navigation bar at the top of the calendar, including styles for its buttons and the title.
  • - The calendar grid itself, using CSS Grid for a 7-column layout.
  • - Individual weekday headers and day cells, with alternating row shading and highlighting for the current day.
  • - Event icons and their tooltips, which will show event descriptions on hover.

4. Bringing it to life with JavaScript

Let's add a <script> block to your index.html, right before the closing </body> tag. This is where we'll bring the calendar to life with JavaScript! ✨ We'll start by defining configuration settings and event data, then write the logic that generates the calendar, handles month switching, and highlights events.

Step 1: Add the script block:

<script>
  // We'll paste the following JavaScript blocks here
</script>

Step 2: Add the calendar configuration and sample events:

// Calendar configuration
const calendarConfig = {
  showNav: true, // Show month navigation? true or false
  prevLabel: "❮", // Symbol for the Previous month
  nextLabel: "❯", // Symbol for the Next month
  locale: "en", // Change to "de", "fr", etc. if needed
  weekStartsOn: 0 // 0 = Sunday, 1 = Monday
};

// Events for specific dates (one-time)
const events = {
  "2025-04-21": { icon: "✝️", text: "Easter Monday" }
};

// Recurring events every year (e.g. birthdays, holidays)
const recurringEvents = {
  "01-01": { icon: "🎉", text: "New Year's Day" },
  "05-13": { icon: "🎂", text: "Nick's Birthday" },
  "10-31": { icon: "🎃", text: "Danny's Birthday & Halloween" },
  "12-24": { icon: "🎄", text: "Christmas" },
  "06-01": { icon: "🏳️‍🌈", text: "Pride Month" }
};

Step 3: Add the functions to generate the calendar:

// Weekday headers
function getWeekdays(startDay = 0) {
    const days = ["Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"];
    return days.slice(startDay).concat(days.slice(0, startDay));
}

const calendarGrid = document.getElementById("calendar");
const navigationBar = document.getElementById("calendar-nav");

let currentDate = new Date();

// Utility to create HTML elements for the calendar
const createElement = (tag, text = "", className = "") => {
    const element = document.createElement(tag);

    if (text) element.textContent = text;
    if (className) element.className = className;
    return element;
};

// Main function to build the calendar
function buildCalendar(date) {
    calendarGrid.innerHTML = "";
    navigationBar.innerHTML = "";

    const year = date.getFullYear();
    const month = date.getMonth();
    const today = new Date();

    // Get localized month name
    const monthName = date.toLocaleString(calendarConfig.locale || "en", {
        month: "long",
    });

    // Create title and navigation buttons
    const titleElement = createElement("div", `${monthName} ${year}`, "");
    titleElement.id = "calendar-title";

    if (calendarConfig.showNav) {
        const prevButton = createElement("button", calendarConfig.prevLabel);
        prevButton.onclick = () => changeMonth(-1);

        const nextButton = createElement("button", calendarConfig.nextLabel);
        nextButton.onclick = () => changeMonth(1);

        navigationBar.append(prevButton, titleElement, nextButton);
    } else {
        navigationBar.appendChild(titleElement);
    }

    // Add weekday headers
    getWeekdays(calendarConfig.weekStartsOn).forEach((day) =>
        calendarGrid.appendChild(createElement("div", day, "weekday"))
    );

    let firstDay = new Date(year, month, 1).getDay();
    firstDay = (firstDay - calendarConfig.weekStartsOn + 7) % 7;
    const daysInMonth = new Date(year, month + 1, 0).getDate();
    const daysInPrevMonth = new Date(year, month, 0).getDate();

    const totalCells = firstDay + daysInMonth;
    const totalWeeks = Math.ceil(totalCells / 7);
    const neededCells = totalWeeks * 7;

    // Fill in previous month days (greyed out)
    for (let i = firstDay - 1; i >= 0; i--) {
        const day = daysInPrevMonth - i;
        const dayCell = createElement("div", "", "day adjacent-month");
        const container = createElement("div", "", "number-container");

        container.textContent = day;
        dayCell.appendChild(container);
        calendarGrid.appendChild(dayCell);
    }

    // Fill in current month days
    for (let day = 1; day <= daysInMonth; day++) {
        const dayCell = createElement("div", "", "day");

        // Alternate row styling
        if (Math.floor((firstDay + day - 1) / 7) % 2 === 1) {
            dayCell.classList.add("alt-row");
        }

        const dateString = `${year}-${String(month + 1).padStart(2, "0")}-${String(day).padStart(2, "0")}`;
        const recurringKey = `${String(month + 1).padStart(2, "0")}-${String(day).padStart(2, "0")}`;
        const container = createElement("div", "", "number-container");

        // Check for event (specific or recurring)
        const event = events[dateString] || recurringEvents[recurringKey];

        if (event) {
            const iconSpan = createElement("span", event.icon, "event-icon");
            const tooltip = createElement("span", event.text, "tooltip");

            iconSpan.appendChild(tooltip);
            container.appendChild(iconSpan);
        } else {
            container.textContent = day;
        }

        dayCell.appendChild(container);

        // Highlight current day
        if (
            day === today.getDate() &&
            month === today.getMonth() &&
            year === today.getFullYear()
        ) {
            dayCell.classList.add("current-day");
        }

        calendarGrid.appendChild(dayCell);
    }

    // Fill in next month days to complete row
    const filledCells = calendarGrid.querySelectorAll(".day").length;
    const nextMonthDays = neededCells - filledCells;

    for (let day = 1; day <= nextMonthDays; day++) {
        const dayCell = createElement("div", "", "day adjacent-month");
        const container = createElement("div", "", "number-container");

        container.textContent = day;
        dayCell.appendChild(container);
        calendarGrid.appendChild(dayCell);
    }
}

// Function to change calendar month
function changeMonth(offset) {
    currentDate.setMonth(currentDate.getMonth() + offset);
    buildCalendar(currentDate);
}

// Show the calendar on page load
buildCalendar(currentDate);

Let's break down the key parts of this JavaScript in case you're interested in that:

  • calendarConfig: Settings like whether to show navigation buttons, the labels for those buttons, and the calendar's locale for month names.
  • events: One time events, won't be repeated.
  • recurringEvents: Recurring events, will be added automatically every year.
  • weekdays: Weekday labels.
  • createElement helper function: To simplify creating new HTML elements.
  • buildCalendar(date): Core function that generates the calendar.
  • changeMonth(offset): Changes the month with the navigation.
  • buildCalendar(currentDate): Displays the calendar when the page loads.

5. The full code

Here's the complete index.html file with all the HTML, CSS, and JavaScript combined. You can save this code as index.html and open it in your web browser to see your dynamic event calendar in action! Just replace the sample events with your own and voilà—you're done! ✨

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <title>Event Calendar</title>
    <style>
      /* Calendar Container */
      #calendar-container {
        background: #fff;
        border: 1px solid rgba(0,0,0, 0.1);
        width: max-content;
        max-width: 100%;
      }

      /* Calendar Navigation */
      #calendar-nav {
        background: #0e4a5f;
        color: #fff;
        font-weight: bold;
        text-transform: uppercase;
        letter-spacing: 1px;
        display: flex;
        justify-content: space-between;
        align-items: center;
        padding: 5px 10px;
      }

      #calendar-nav button {
        background: transparent;
        border: 0;
        color: inherit;
        font-size: 8pt;
        cursor: pointer;
      }

      #calendar-title {
        text-align: center;
        display: block;
        width: 100%;
      }

      /* Calendar Grid */
      #calendar {
        display: grid;
        grid-template-columns: repeat(7, 1fr);
      }

      /* Weekday Header */
      .weekday {
        background: #f0f0f0;
        font-weight: bold;
        padding: 5px;
        text-transform: uppercase;
        border-bottom: 1px solid #ccc;
        text-align: center;
      }

      /* Day Cells */
      .day {
        display: flex;
        justify-content: center;
        align-items: center;
        height: 30px;
        position: relative;
        background: rgba(255, 255, 255, 0.05);
        box-sizing: border-box;
        padding: 0 5px;
        text-align: center;
      }

      .alt-row {
        background: rgba(0, 0, 0, 0.05);
      }

      .current-day {
        font-weight: bold;
        background: #fff;
        border: 1px solid #ccc;
        box-shadow: 0 0 8px rgba(0, 0, 0, 0.1);
      }

      .adjacent-month {
        opacity: 0.4;
      }

      .number-container {
        display: flex;
        align-items: center;
        gap: 4px;
      }

      /* Event Icons and Tooltips */
      .event-icon {
        cursor: help;
        display: inline-block;
        position: relative;
      }

      .tooltip {
        display: none;
        position: absolute;
        left: 20px;
        background: #333;
        color: #fff;
        padding: 5px 10px;
        white-space: nowrap;
        border-radius: 3px;
        z-index: 10;
      }

      .event-icon:hover .tooltip {
        display: block;
      }
    </style>
  </head>
  <body>
    <div id="calendar-container">
      <div id="calendar-nav"></div>
      <div id="calendar"></div>
    </div>

    <script>
      // Calendar configuration
      const calendarConfig = {
        showNav: true, // Show month navigation? true or false
        prevLabel: "❮", // Symbol for the Prev month link
        nextLabel: "❯", // Symbol for the Next month link
        locale: "en", // Change to "de", "fr", etc. if needed
        weekStartsOn: 0, // 0 = Sunday, 1 = Monday
      };

      // Events for specific dates (one-time)
      const events = {
        "2025-04-21": { icon: "✝️", text: "Easter Monday" },
      };

      // Recurring events every year (e.g. birthdays)
      const recurringEvents = {
        "01-01": { icon: "🎉", text: "New Year's Day" },
        "05-13": { icon: "🎂", text: "Nick's Birthday" },
        "10-31": { icon: "🎃", text: "Danny's Birthday & Halloween" },
        "12-24": { icon: "🎄", text: "Christmas" },
        "06-01": { icon: "🏳️‍🌈", text: "Pride Month" },
      };

      // Weekday headers
      function getWeekdays(startDay = 0) {
        const days = ["Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"];
        return days.slice(startDay).concat(days.slice(0, startDay));
      }

      const calendarGrid = document.getElementById("calendar");
      const navigationBar = document.getElementById("calendar-nav");

      let currentDate = new Date();

      // Utility to create HTML elements for the calendar
      const createElement = (tag, text = "", className = "") => {
        const element = document.createElement(tag);

        if (text) element.textContent = text;
        if (className) element.className = className;
        return element;
      };

      // Main function to build the calendar
      function buildCalendar(date) {
        calendarGrid.innerHTML = "";
        navigationBar.innerHTML = "";

        const year = date.getFullYear();
        const month = date.getMonth();
        const today = new Date();

        // Get localized month name
        const monthName = date.toLocaleString(calendarConfig.locale || "en", {
          month: "long",
        });

        // Create title and navigation buttons
        const titleElement = createElement("div", `${monthName} ${year}`, "");
        titleElement.id = "calendar-title";

        if (calendarConfig.showNav) {
          const prevButton = createElement("button", calendarConfig.prevLabel);
          prevButton.onclick = () => changeMonth(-1);

          const nextButton = createElement("button", calendarConfig.nextLabel);
          nextButton.onclick = () => changeMonth(1);

          navigationBar.append(prevButton, titleElement, nextButton);
        } else {
          navigationBar.appendChild(titleElement);
        }

        // Add weekday headers
        getWeekdays(calendarConfig.weekStartsOn).forEach((day) =>
          calendarGrid.appendChild(createElement("div", day, "weekday"))
        );

        let firstDay = new Date(year, month, 1).getDay();
        firstDay = (firstDay - calendarConfig.weekStartsOn + 7) % 7;
        const daysInMonth = new Date(year, month + 1, 0).getDate();
        const daysInPrevMonth = new Date(year, month, 0).getDate();

        const totalCells = firstDay + daysInMonth;
        const totalWeeks = Math.ceil(totalCells / 7);
        const neededCells = totalWeeks * 7;

        // Fill in previous month days (greyed out)
        for (let i = firstDay - 1; i >= 0; i--) {
          const day = daysInPrevMonth - i;
          const dayCell = createElement("div", "", "day adjacent-month");
          const container = createElement("div", "", "number-container");

          container.textContent = day;
          dayCell.appendChild(container);
          calendarGrid.appendChild(dayCell);
        }

        // Fill in current month days
        for (let day = 1; day <= daysInMonth; day++) {
          const dayCell = createElement("div", "", "day");

          // Alternate row styling
          if (Math.floor((firstDay + day - 1) / 7) % 2 === 1) {
            dayCell.classList.add("alt-row");
          }

          const dateString = `${year}-${String(month + 1).padStart(2,"0")}-${String(day).padStart(2, "0")}`;
          const recurringKey = `${String(month + 1).padStart(2, "0")}-${String(day).padStart(2, "0")}`;
          const container = createElement("div", "", "number-container");

          // Check for event (specific or recurring)
          const event = events[dateString] || recurringEvents[recurringKey];

          if (event) {
            const iconSpan = createElement("span", event.icon, "event-icon");
            const tooltip = createElement("span", event.text, "tooltip");

            iconSpan.appendChild(tooltip);
            container.appendChild(iconSpan);
          } else {
            container.textContent = day;
          }

          dayCell.appendChild(container);

          // Highlight current day
          if (
            day === today.getDate() &&
            month === today.getMonth() &&
            year === today.getFullYear()
          ) {
            dayCell.classList.add("current-day");
          }

          calendarGrid.appendChild(dayCell);
        }

        // Fill in next month days to complete row
        const filledCells = calendarGrid.querySelectorAll(".day").length;
        const nextMonthDays = neededCells - filledCells;

        for (let day = 1; day <= nextMonthDays; day++) {
          const dayCell = createElement("div", "", "day adjacent-month");
          const container = createElement("div", "", "number-container");

          container.textContent = day;
          dayCell.appendChild(container);
          calendarGrid.appendChild(dayCell);
        }
      }

      // Function to change calendar month
      function changeMonth(offset) {
        currentDate.setMonth(currentDate.getMonth() + offset);
        buildCalendar(currentDate);
      }

      // Show the calendar on page load
      buildCalendar(currentDate);
    </script>
  </body>
</html>