Dynamic Event Calendar: Update Guide
I recently updated the Dynamic Event Calendar to add clickable event links (a wish from Sybilla on the wishlist) and support for HTML font icons.
If you are already using the older version on your site, this step-by-step guide will show you how to easily add both new features manually without losing your current setup. <3
Prefer to just swap out your files instead of editing your code line by line? You can still find the full, completely updated script at the bottom of this page.
1. Update the event datasets
Let's first assign a link to an event! Find your events and recurringEvents objects and add a url property to any event that needs a link.
// 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" }
};
Simply add the url property to any event you want.
For example, I've added it for Easter Monday and my birthday:
// Events for specific dates (one-time)
const events = {
"2025-04-21": { icon: "✝️", text: "Easter Monday", url: "https://example.com/easter" },
};
// Recurring events every year (e.g. birthdays)
const recurringEvents = {
"01-01": { icon: "🎉", text: "New Year's Day" },
"05-13": { icon: "🎂", text: "Nick's Birthday", url: "https://example.com/nick" },
"10-31": { icon: "🎃", text: "Danny's Birthday & Halloween" },
"12-24": { icon: "🎄", text: "Christmas" },
"06-01": { icon: "🏳️🌈", text: "Pride Month" },
};
2. Update the icon element creation logic
Next, we'll adjust how the icons are created. Instead of always generating a standard <span>, the calendar will now dynamically use an anchor <a> tag when a link is present, and it will use innerHTML so it can render actual HTML tags instead of treating them like plain text!
Inside the buildCalendar function, locate the if (event) block inside your loop, which currently looks like this:
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;
}
To add both the link functionality and font icon support, replace that entire block with the updated version below:
if (event) {
const iconTag = event.url ? "a" : "span";
const iconSpan = createElement(iconTag, "", "event-icon");
iconSpan.innerHTML = event.icon;
if (event.url) {
iconSpan.href = event.url;
iconSpan.target = "_blank";
}
const tooltip = createElement("span", event.text, "tooltip");
iconSpan.appendChild(tooltip);
container.appendChild(iconSpan);
} else {
container.textContent = day;
}
With this change, you can now freely mix emojis, icon font HTML from Font Awesome, or even custom image tags! Emojis will still continue to work exactly like before.
Careful, though - because your icon HTML is already wrapped in double quotes (" "), always use single quotes (') for the classes inside it. Mixing double quotes inside double quotes will break your JavaScript.
This works: icon: "<i class='fa-solid fa-cake-candles'></i>"
This breaks: icon: "<i class="fa-solid fa-cake-candles"></i>"
To make icon libraries work, do not forget to link their stylesheet inside your website's <head> section first:
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/7.0.1/css/all.min.css" />
4. (Optional) Fix the link style
This step can be skipped if you already have text-decoration: none; for links (a { }) in your CSS file.
If not, find this CSS inside your <style> block:
.event-icon {
cursor: help;
display: inline-block;
position: relative;
}
And add text-decoration: none; here, to remove the default link underline style.
.event-icon {
cursor: help;
display: inline-block;
position: relative;
text-decoration: none;
}
And that's it! I hope this was easy to follow, and if you have any questions or need some help, feel free to write us again! <3
For completeness, here is the full updated code for the calendar:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<title>Event Calendar</title>
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/7.0.1/css/all.min.css" />
<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;
text-decoration: none;
}
.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", url: "https://example.com/easter" }, // optional: Events as linked icons
};
// 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: "<i class='fa-solid fa-cake'></i>", text: "Danny's Birthday & Halloween" }, // optional: HTML instead of Emojis using FontAwesome
"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 iconTag = event.url ? "a" : "span";
const iconSpan = createElement(iconTag, "", "event-icon");
iconSpan.innerHTML = event.icon;
if (event.url) {
iconSpan.href = event.url;
iconSpan.target = "_blank";
}
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>