Dynamic Event Calendar
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.createElementhelper 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>