custom calendar
event divs
web development
UI design
JavaScript

Custom calendar with event divs

Master System Design with Codemia

Enhance your system design skills with over 120 practice problems, detailed solutions, and hands-on exercises.

Introduction

Building a custom calendar with event divs gives you full control over layout, styling, and interaction that pre-built libraries cannot match. The approach involves generating a month grid in HTML (using a table or CSS Grid), rendering event blocks as positioned div elements within day cells, and handling navigation and event CRUD with JavaScript. This article walks through building a functional monthly calendar from scratch with HTML, CSS, and vanilla JavaScript, including event display, multi-day events, and click interactions.

HTML Structure

html
1<div class="calendar-container">
2  <div class="calendar-header">
3    <button id="prev-month">&lt;</button>
4    <h2 id="month-year">March 2026</h2>
5    <button id="next-month">&gt;</button>
6  </div>
7
8  <div class="calendar-grid">
9    <div class="day-header">Sun</div>
10    <div class="day-header">Mon</div>
11    <div class="day-header">Tue</div>
12    <div class="day-header">Wed</div>
13    <div class="day-header">Thu</div>
14    <div class="day-header">Fri</div>
15    <div class="day-header">Sat</div>
16    <!-- Day cells generated by JavaScript -->
17  </div>
18</div>

CSS Styling

css
1.calendar-container {
2  max-width: 900px;
3  margin: 0 auto;
4  font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif;
5}
6
7.calendar-header {
8  display: flex;
9  justify-content: space-between;
10  align-items: center;
11  padding: 16px;
12}
13
14.calendar-header button {
15  background: none;
16  border: 1px solid #ddd;
17  border-radius: 4px;
18  padding: 8px 16px;
19  cursor: pointer;
20  font-size: 16px;
21}
22
23.calendar-grid {
24  display: grid;
25  grid-template-columns: repeat(7, 1fr);
26  border: 1px solid #e0e0e0;
27}
28
29.day-header {
30  padding: 8px;
31  text-align: center;
32  font-weight: 600;
33  background: #f5f5f5;
34  border-bottom: 1px solid #e0e0e0;
35}
36
37.day-cell {
38  min-height: 120px;
39  border: 1px solid #e0e0e0;
40  padding: 4px;
41  position: relative;  /* For positioning event divs */
42}
43
44.day-cell.other-month {
45  background: #fafafa;
46  color: #ccc;
47}
48
49.day-cell.today {
50  background: #e3f2fd;
51}
52
53.day-number {
54  font-size: 14px;
55  font-weight: 500;
56  margin-bottom: 4px;
57}
58
59.event-div {
60  background: #1976d2;
61  color: white;
62  padding: 2px 6px;
63  margin: 2px 0;
64  border-radius: 3px;
65  font-size: 12px;
66  cursor: pointer;
67  overflow: hidden;
68  white-space: nowrap;
69  text-overflow: ellipsis;
70}
71
72.event-div:hover {
73  opacity: 0.85;
74}
75
76.event-div.multi-day {
77  border-radius: 0;
78  margin-right: -4px;
79  margin-left: -4px;
80}
81
82.event-div.multi-day.start {
83  border-radius: 3px 0 0 3px;
84  margin-left: 0;
85}
86
87.event-div.multi-day.end {
88  border-radius: 0 3px 3px 0;
89  margin-right: 0;
90}
91
92/* Event color categories */
93.event-div.work { background: #1976d2; }
94.event-div.personal { background: #388e3c; }
95.event-div.urgent { background: #d32f2f; }
96.event-div.meeting { background: #7b1fa2; }

JavaScript: Calendar Generation

javascript
1class Calendar {
2  constructor(container) {
3    this.container = container;
4    this.currentDate = new Date();
5    this.events = [];
6    this.init();
7  }
8
9  init() {
10    document.getElementById("prev-month").addEventListener("click", () => {
11      this.currentDate.setMonth(this.currentDate.getMonth() - 1);
12      this.render();
13    });
14
15    document.getElementById("next-month").addEventListener("click", () => {
16      this.currentDate.setMonth(this.currentDate.getMonth() + 1);
17      this.render();
18    });
19
20    this.render();
21  }
22
23  render() {
24    const year = this.currentDate.getFullYear();
25    const month = this.currentDate.getMonth();
26
27    // Update header
28    const monthNames = [
29      "January", "February", "March", "April", "May", "June",
30      "July", "August", "September", "October", "November", "December",
31    ];
32    document.getElementById("month-year").textContent =
33      `${monthNames[month]} ${year}`;
34
35    // Calculate grid
36    const firstDay = new Date(year, month, 1).getDay();
37    const daysInMonth = new Date(year, month + 1, 0).getDate();
38    const daysInPrevMonth = new Date(year, month, 0).getDate();
39
40    // Clear existing day cells
41    const grid = document.querySelector(".calendar-grid");
42    grid.querySelectorAll(".day-cell").forEach((el) => el.remove());
43
44    // Previous month's trailing days
45    for (let i = firstDay - 1; i >= 0; i--) {
46      const day = daysInPrevMonth - i;
47      this.createDayCell(grid, day, year, month - 1, true);
48    }
49
50    // Current month days
51    const today = new Date();
52    for (let day = 1; day <= daysInMonth; day++) {
53      const isToday =
54        day === today.getDate() &&
55        month === today.getMonth() &&
56        year === today.getFullYear();
57      this.createDayCell(grid, day, year, month, false, isToday);
58    }
59
60    // Next month's leading days
61    const totalCells = firstDay + daysInMonth;
62    const remaining = totalCells % 7 === 0 ? 0 : 7 - (totalCells % 7);
63    for (let day = 1; day <= remaining; day++) {
64      this.createDayCell(grid, day, year, month + 1, true);
65    }
66  }
67
68  createDayCell(grid, day, year, month, isOtherMonth, isToday = false) {
69    const cell = document.createElement("div");
70    cell.className = "day-cell";
71    if (isOtherMonth) cell.classList.add("other-month");
72    if (isToday) cell.classList.add("today");
73
74    const number = document.createElement("div");
75    number.className = "day-number";
76    number.textContent = day;
77    cell.appendChild(number);
78
79    // Add events for this day
80    const dateStr = `${year}-${String(month + 1).padStart(2, "0")}-${String(day).padStart(2, "0")}`;
81    const dayEvents = this.getEventsForDate(dateStr);
82    dayEvents.forEach((event) => {
83      const eventDiv = document.createElement("div");
84      eventDiv.className = `event-div ${event.category || ""}`;
85      eventDiv.textContent = event.title;
86      eventDiv.addEventListener("click", (e) => {
87        e.stopPropagation();
88        this.onEventClick(event);
89      });
90      cell.appendChild(eventDiv);
91    });
92
93    // Click to add event
94    cell.addEventListener("click", () => {
95      if (!isOtherMonth) {
96        this.onDayClick(dateStr);
97      }
98    });
99
100    grid.appendChild(cell);
101  }
102
103  addEvent(event) {
104    this.events.push(event);
105    this.render();
106  }
107
108  getEventsForDate(dateStr) {
109    return this.events.filter((e) => e.date === dateStr);
110  }
111
112  onDayClick(dateStr) {
113    const title = prompt(`Add event for ${dateStr}:`);
114    if (title) {
115      this.addEvent({ title, date: dateStr, category: "work" });
116    }
117  }
118
119  onEventClick(event) {
120    alert(`Event: ${event.title}\nDate: ${event.date}`);
121  }
122}
123
124// Initialize
125const calendar = new Calendar(document.querySelector(".calendar-container"));
126
127// Add sample events
128calendar.addEvent({ title: "Team Meeting", date: "2026-03-05", category: "meeting" });
129calendar.addEvent({ title: "Deploy v2.0", date: "2026-03-12", category: "work" });
130calendar.addEvent({ title: "Doctor Appt", date: "2026-03-18", category: "personal" });
131calendar.addEvent({ title: "Deadline", date: "2026-03-25", category: "urgent" });

Multi-Day Events

javascript
1// Add to Calendar class
2renderMultiDayEvent(event) {
3  const start = new Date(event.startDate);
4  const end = new Date(event.endDate);
5  const cells = document.querySelectorAll('.day-cell');
6
7  cells.forEach(cell => {
8    const cellDate = cell.dataset.date;
9    if (!cellDate) return;
10
11    const current = new Date(cellDate);
12    if (current >= start && current <= end) {
13      const div = document.createElement('div');
14      div.className = `event-div multi-day ${event.category || ''}`;
15      div.textContent = current.getTime() === start.getTime()
16        ? event.title
17        : '';
18
19      if (current.getTime() === start.getTime()) div.classList.add('start');
20      if (current.getTime() === end.getTime()) div.classList.add('end');
21
22      cell.appendChild(div);
23    }
24  });
25}

Event Data Model

javascript
1// Example event structure
2const events = [
3  {
4    id: 1,
5    title: "Sprint Planning",
6    date: "2026-03-02",
7    startTime: "09:00",
8    endTime: "10:30",
9    category: "meeting",
10    color: "#7b1fa2",
11  },
12  {
13    id: 2,
14    title: "Conference",
15    startDate: "2026-03-15",
16    endDate: "2026-03-17",
17    category: "work",
18    allDay: true,
19  },
20];

Common Pitfalls

  • Off-by-one errors in month calculations: JavaScript months are 0-indexed (January = 0). new Date(2026, 2, 1) is March 1st, not February 1st. The daysInMonth trick new Date(year, month + 1, 0).getDate() uses day 0 to get the last day of the previous month.
  • Not handling timezone differences for date comparisons: new Date("2026-03-15") is parsed as UTC midnight, which may be a different local date depending on the user's timezone. Use new Date(year, month, day) for local dates, or normalize all dates to the same timezone.
  • Forgetting position: relative on day cells: Event divs positioned with position: absolute inside a day cell require the cell to have position: relative. Without it, events are positioned relative to the nearest positioned ancestor, breaking the layout.
  • Rendering too many events without overflow handling: If a day has more than 3-4 events, the cell overflows. Add a "+N more" indicator and a max-height with overflow hidden to keep the grid aligned across rows.
  • Rebuilding the entire DOM on every interaction: Calling render() after every event addition destroys and recreates all cells, which is slow with many events. Use targeted DOM updates (modify only affected cells) or a virtual DOM library for better performance.

Summary

  • Build the calendar grid using CSS Grid with 7 columns for days of the week
  • Generate day cells dynamically based on the current month, including trailing/leading days from adjacent months
  • Render events as positioned div elements inside day cells, styled with category-specific colors
  • Handle multi-day events by spanning event divs across consecutive cells with border-radius adjustments
  • Use position: relative on day cells and handle overflow with "+N more" indicators for busy days

Course illustration
Course illustration

All Rights Reserved.