FORMS

Interactive Schedule Calendar App

A responsive schedule calendar with month navigation, daily tasks, notes, search and an interactive add schedule form.

6:13

AUGUST 2026

SUN MON TUE WED THU FRI SAT

August 31, 2026’s Schedule

2 items

New Schedule

For September 1, 2026

HTML
<div class="phone">

  <div class="status-bar">

    <span class="time" id="statusTime">6:13</span>

    <div class="status-icons">

      <svg width="18" height="10" viewBox="0 0 18 10" fill="none">
        <rect x="0" y="6" width="3" height="4" rx="0.5" fill="#fff"></rect>
        <rect x="4.5" y="4" width="3" height="6" rx="0.5" fill="#fff"></rect>
        <rect x="9" y="2" width="3" height="8" rx="0.5" fill="#fff"></rect>
        <rect x="13.5" y="0" width="3" height="10" rx="0.5" fill="#fff"></rect>
      </svg>

      <svg width="20" height="12" viewBox="0 0 20 12" fill="none">
        <rect x="0.5" y="0.5" width="17" height="11" rx="2.5" stroke="#fff"></rect>
        <rect x="2" y="2" width="14" height="8" rx="1" fill="#fff"></rect>
        <rect x="18" y="4" width="1.5" height="4" rx="0.75" fill="#fff"></rect>
      </svg>

    </div>

  </div>


  <div class="tabs">

    <button
      class="tab active"
      data-tab="schedule"
      type="button">
      Schedule
    </button>

    <button
      class="tab"
      data-tab="note"
      type="button">
      Note
    </button>

  </div>


  <section
    class="view schedule-view active"
    id="scheduleView">

    <div class="month-header">

      <button
        class="nav-btn"
        id="prevMonth"
        type="button"
        aria-label="Previous month">
        &#8249;
      </button>

      <h1
        class="month-title"
        id="monthTitle">
        AUGUST 2026
      </h1>

      <button
        class="nav-btn"
        id="nextMonth"
        type="button"
        aria-label="Next month">
        &#8250;
      </button>

    </div>


    <div class="weekdays">
      <span>SUN</span>
      <span>MON</span>
      <span>TUE</span>
      <span>WED</span>
      <span>THU</span>
      <span>FRI</span>
      <span>SAT</span>
    </div>


    <div
      class="calendar-grid"
      id="calendarGrid">
    </div>


    <div class="schedule-list-header">

      <h2 id="scheduleListTitle">
        August 31, 2026's Schedule
      </h2>

      <span
        class="item-count"
        id="itemCount">
        2 items
      </span>

    </div>


    <div
      class="schedule-list"
      id="scheduleList">
    </div>

  </section>


  <section
    class="view note-view"
    id="noteView">

    <div class="search-bar">

      <svg
        class="search-icon"
        width="18"
        height="18"
        viewBox="0 0 24 24"
        fill="none"
        stroke="currentColor"
        stroke-width="2">

        <circle cx="11" cy="11" r="7"></circle>

        <line
          x1="21"
          y1="21"
          x2="16.65"
          y2="16.65">
        </line>

      </svg>

      <input
        type="text"
        id="searchInput"
        placeholder="Search schedule..."
        autocomplete="off">

    </div>

    <div
      class="note-results"
      id="noteResults">
    </div>

  </section>


  <button
    class="fab"
    id="fabAdd"
    type="button"
    aria-label="Add schedule">
    +
  </button>


  <div class="home-indicator"></div>


  <div
    class="sheet-backdrop"
    id="sheetBackdrop">

    <div
      class="sheet"
      id="sheet">

      <div class="sheet-handle"></div>

      <h2 class="sheet-title">
        New Schedule
      </h2>

      <p
        class="sheet-subtitle"
        id="sheetSubtitle">
        For September 1, 2026
      </p>


      <label
        class="field-label"
        for="titleInput">
        Title
      </label>

      <input
        type="text"
        id="titleInput"
        class="field-input"
        placeholder="e.g. Team meeting">


      <div class="field-row">

        <div class="field-col">

          <label
            class="field-label"
            for="startTimeInput">
            Start time
          </label>

          <div class="time-input-wrap">

            <input
              type="text"
              id="startTimeInput"
              class="field-input"
              value="09:00 AM"
              readonly>

          </div>

        </div>


        <div class="field-col">

          <label
            class="field-label"
            for="endTimeInput">
            End time
          </label>

          <div class="time-input-wrap">

            <input
              type="text"
              id="endTimeInput"
              class="field-input"
              value="10:00 AM"
              readonly>

          </div>

        </div>

      </div>


      <label
        class="field-label"
        for="placeInput">
        Place
      </label>

      <input
        type="text"
        id="placeInput"
        class="field-input"
        placeholder="e.g. Office Room">


      <label
        class="field-label"
        for="notesInput">
        Notes
      </label>

      <textarea
        id="notesInput"
        class="field-input field-textarea"
        placeholder="Write any extra details..."></textarea>


      <div class="sheet-actions">

        <button
          class="btn btn-cancel"
          id="cancelBtn"
          type="button">
          Cancel
        </button>

        <button
          class="btn btn-save"
          id="saveBtn"
          type="button">
          Save
        </button>

      </div>

    </div>

  </div>

</div>
CSS
:root{
  --cr-bg:#050914;
  --cr-phone:#070d19;
  --cr-card:#0b1423;
  --cr-field:#0d1727;
  --cr-border:#1c2a40;
  --cr-cyan:#22d3ee;
  --cr-purple:#a855f7;
  --cr-text:#ffffff;
  --cr-muted:#94a3b8;
  --cr-faint:#46556c;
  --radius-lg:28px;
  --radius-md:16px;
  --radius-sm:12px;
  font-family:Inter,Arial,sans-serif;
}

*{
  margin:0;
  padding:0;
  box-sizing:border-box;
}

body{
  min-height:100vh;
  display:flex;
  align-items:center;
  justify-content:center;
  padding:24px;
  background:#020611;
  color:#ffffff;
}

.phone{
  position:relative;
  width:360px;
  height:640px;
  display:flex;
  flex-direction:column;
  overflow:hidden;
  border:1px solid rgba(34,211,238,.22);
  border-radius:40px;
  background:
    radial-gradient(
      circle at 50% 0%,
      rgba(34,211,238,.055),
      transparent 28%
    ),
    linear-gradient(
      180deg,
      #09111f 0%,
      #050914 100%
    );
  box-shadow:
    0 0 35px rgba(34,211,238,.08),
    0 0 70px rgba(168,85,247,.07),
    0 30px 70px rgba(0,0,0,.65);
}

.status-bar{
  display:flex;
  align-items:center;
  justify-content:space-between;
  padding:18px 22px 6px;
  color:#ffffff;
}

.status-bar .time{
  font-size:15px;
  font-weight:700;
}

.status-icons{
  display:flex;
  align-items:center;
  gap:8px;
}

.tabs{
  display:flex;
  gap:6px;
  margin:14px 24px 18px;
  padding:5px;
  border:1px solid var(--cr-border);
  border-radius:999px;
  background:#08111f;
}

.tab{
  flex:1;
  padding:10px 0;
  border:0;
  border-radius:999px;
  background:transparent;
  color:var(--cr-muted);
  font-family:inherit;
  font-size:14px;
  font-weight:700;
  cursor:pointer;
}

.tab.active{
  color:#ffffff;
  background:
    linear-gradient(
      135deg,
      #06b6d4,
      #6366f1 52%,
      #a855f7
    );
  box-shadow:
    0 0 16px rgba(34,211,238,.24),
    0 0 26px rgba(168,85,247,.16);
}

.view{
  display:none;
  flex:1;
  overflow-y:auto;
  padding:0 22px 90px;
}

.view.active{
  display:block;
}

.view::-webkit-scrollbar{
  display:none;
}

.month-header{
  display:flex;
  align-items:center;
  justify-content:center;
  gap:18px;
  margin-bottom:14px;
}

.month-title{
  color:var(--cr-cyan);
  font-size:15px;
  font-weight:800;
  letter-spacing:1.5px;
}

.nav-btn{
  width:30px;
  height:30px;
  display:flex;
  align-items:center;
  justify-content:center;
  border:1px solid #24344d;
  border-radius:50%;
  background:#0b1423;
  color:#ffffff;
  cursor:pointer;
}

.weekdays{
  display:grid;
  grid-template-columns:repeat(7,1fr);
  margin-bottom:8px;
  text-align:center;
}

.weekdays span{
  color:var(--cr-muted);
  font-size:11px;
  font-weight:700;
}

.weekdays span:first-child{
  color:var(--cr-purple);
}

.calendar-grid{
  display:grid;
  grid-template-columns:repeat(7,1fr);
  row-gap:14px;
  margin-bottom:22px;
}

.day-cell{
  position:relative;
  height:32px;
  display:flex;
  align-items:center;
  justify-content:center;
  color:#ffffff;
  font-size:14px;
  cursor:pointer;
}

.day-cell.other-month{
  color:var(--cr-faint);
}

.day-cell.sunday:not(.other-month){
  color:var(--cr-purple);
}

.day-cell.today-dot::after{
  content:"";
  position:absolute;
  bottom:-6px;
  left:50%;
  width:4px;
  height:4px;
  transform:translateX(-50%);
  border-radius:50%;
  background:var(--cr-cyan);
}

.day-cell .day-num{
  width:32px;
  height:32px;
  display:flex;
  align-items:center;
  justify-content:center;
  border-radius:50%;
}

.day-cell.selected .day-num{
  color:#ffffff;
  font-weight:800;
  background:
    linear-gradient(
      135deg,
      #06b6d4,
      #6366f1 55%,
      #a855f7
    );
  box-shadow:
    0 0 12px rgba(34,211,238,.30),
    0 0 22px rgba(168,85,247,.20);
}

.schedule-list-header{
  display:flex;
  align-items:baseline;
  justify-content:space-between;
  margin-bottom:14px;
}

.schedule-list-header h2{
  color:#ffffff;
  font-size:16px;
  font-weight:800;
}

.item-count{
  color:var(--cr-muted);
  font-size:12px;
}

.schedule-list{
  display:flex;
  flex-direction:column;
  gap:14px;
}

.schedule-card{
  display:flex;
  gap:14px;
  padding-left:6px;
}

.timeline{
  width:26px;
  display:flex;
  flex-direction:column;
  align-items:center;
}

.timeline .dot{
  width:26px;
  height:26px;
  display:flex;
  align-items:center;
  justify-content:center;
  border:2px solid var(--cr-cyan);
  border-radius:50%;
  background:#07101d;
  color:var(--cr-cyan);
  font-size:11px;
  font-weight:800;
}

.timeline .line{
  flex:1;
  width:2px;
  background:
    linear-gradient(
      var(--cr-cyan),
      var(--cr-purple)
    );
}

.card-body{
  flex:1;
  padding:16px;
  border:1px solid var(--cr-border);
  border-radius:16px;
  background:var(--cr-card);
}

.card-top{
  display:flex;
  justify-content:space-between;
  margin-bottom:10px;
}

.card-title{
  color:#ffffff;
  font-size:15px;
  font-weight:800;
}

.checkbox{
  width:20px;
  height:20px;
  display:flex;
  align-items:center;
  justify-content:center;
  border:1.5px solid #52637b;
  border-radius:6px;
  cursor:pointer;
}

.checkbox.checked{
  border-color:var(--cr-cyan);
  background:
    linear-gradient(
      135deg,
      var(--cr-cyan),
      var(--cr-purple)
    );
}

.checkbox.checked::after{
  content:"✓";
  color:#ffffff;
  font-size:12px;
  font-weight:900;
}

.card-row{
  display:flex;
  gap:8px;
  margin-bottom:4px;
  font-size:12.5px;
}

.card-row .label{
  width:40px;
  color:var(--cr-muted);
}

.card-row .value{
  color:#c7d2e5;
}

.empty-state{
  padding:40px 0;
  color:var(--cr-muted);
  font-size:13px;
  text-align:center;
}

.search-bar{
  display:flex;
  align-items:center;
  gap:10px;
  margin:4px 0 18px;
  padding:12px 14px;
  border:1px solid var(--cr-border);
  border-radius:12px;
  background:var(--cr-field);
}

.search-bar input{
  flex:1;
  border:0;
  outline:0;
  background:transparent;
  color:#ffffff;
}

.note-results{
  display:flex;
  flex-direction:column;
  gap:10px;
}

.note-card{
  padding:14px 16px;
  border:1px solid var(--cr-border);
  border-left:3px solid var(--cr-cyan);
  border-radius:10px;
  background:var(--cr-card);
}

.note-card-top{
  display:flex;
  justify-content:space-between;
}

.note-card-title{
  color:#ffffff;
  font-weight:800;
}

.note-card-time{
  color:var(--cr-cyan);
  font-size:12px;
}

.note-card-notes{
  color:var(--cr-muted);
  font-size:12.5px;
}

.fab{
  position:absolute;
  right:22px;
  bottom:34px;
  z-index:20;
  width:54px;
  height:54px;
  display:flex;
  align-items:center;
  justify-content:center;
  border:0;
  border-radius:50%;
  background:
    linear-gradient(
      135deg,
      #22d3ee,
      #6366f1 52%,
      #a855f7
    );
  color:#ffffff;
  font-size:27px;
  cursor:pointer;
  box-shadow:
    0 0 18px rgba(34,211,238,.30),
    0 0 30px rgba(168,85,247,.20);
}

.home-indicator{
  position:absolute;
  bottom:8px;
  left:50%;
  width:110px;
  height:4px;
  transform:translateX(-50%);
  border-radius:2px;
  background:rgba(255,255,255,.24);
}

.sheet-backdrop{
  position:absolute;
  inset:0;
  z-index:30;
  display:flex;
  align-items:flex-end;
  background:rgba(1,4,12,.72);
  backdrop-filter:blur(5px);
  opacity:0;
  pointer-events:none;
}

.sheet-backdrop.open{
  opacity:1;
  pointer-events:auto;
}

.sheet{
  width:100%;
  padding:14px 22px 24px;
  border-top:1px solid rgba(34,211,238,.18);
  border-radius:28px 28px 0 0;
  background:#08111f;
  transform:translateY(100%);
  transition:transform .35s ease;
}

.sheet-backdrop.open .sheet{
  transform:translateY(0);
}

.sheet-handle{
  width:40px;
  height:4px;
  margin:0 auto 18px;
  border-radius:2px;
  background:#34445c;
}

.sheet-title{
  color:var(--cr-cyan);
  font-size:19px;
  font-weight:800;
}

.sheet-subtitle{
  margin-bottom:16px;
  padding-bottom:14px;
  border-bottom:1px solid var(--cr-border);
  color:var(--cr-muted);
  font-size:12.5px;
}

.field-label{
  display:block;
  margin-bottom:7px;
  color:#c7d2e5;
  font-size:12.5px;
  font-weight:700;
}

.field-input{
  width:100%;
  margin-bottom:14px;
  padding:12px 14px;
  border:1.5px solid var(--cr-border);
  border-radius:10px;
  outline:0;
  background:var(--cr-field);
  color:#ffffff;
}

.field-input:focus{
  border-color:var(--cr-cyan);
}

.field-textarea{
  height:70px;
  resize:none;
}

.field-row{
  display:flex;
  gap:12px;
}

.field-col{
  flex:1;
}

.time-input-wrap{
  position:relative;
}

.time-input-wrap .field-input{
  padding-right:36px;
  cursor:pointer;
}

.clock-icon{
  position:absolute;
  right:12px;
  top:50%;
  transform:translateY(-50%);
  color:var(--cr-cyan);
}

.sheet-actions{
  display:flex;
  gap:12px;
}

.btn{
  flex:1;
  padding:14px 0;
  border:0;
  border-radius:12px;
  font-weight:800;
  cursor:pointer;
}

.btn-cancel{
  border:1px solid var(--cr-border);
  background:var(--cr-field);
  color:#ffffff;
}

.btn-save{
  background:
    linear-gradient(
      135deg,
      #06b6d4,
      #6366f1 52%,
      #a855f7
    );
  color:#ffffff;
}

@media(max-width:400px){

  body{
    padding:0;
  }

  .phone{
    width:100vw;
    height:100vh;
    border-radius:0;
  }

}
JavaScript
(() => {

  'use strict';

  const MONTH_NAMES = [
    'JANUARY',
    'FEBRUARY',
    'MARCH',
    'APRIL',
    'MAY',
    'JUNE',
    'JULY',
    'AUGUST',
    'SEPTEMBER',
    'OCTOBER',
    'NOVEMBER',
    'DECEMBER'
  ];

  const MONTH_NAMES_FULL = [
    'January',
    'February',
    'March',
    'April',
    'May',
    'June',
    'July',
    'August',
    'September',
    'October',
    'November',
    'December'
  ];


  let currentYear = 2026;
  let currentMonth = 7;

  let selectedDate = {
    year:2026,
    month:7,
    day:31
  };


  const dateKey = (y,m,d) =>
    `${y}-${m}-${d}`;


  const events = {

    [dateKey(2026,7,31)]: [

      {
        id:'e1',
        title:'Design Review',
        start:'10:30 AM',
        end:'12:00 PM',
        place:'Office Room',
        notes:'Design Review with Product Team',
        done:false
      },

      {
        id:'e2',
        title:'Coffee with Sarah',
        start:'5:00 PM',
        end:'',
        place:'Blue Bottle, 5th Ave',
        notes:'Catch up and talk through the freelance project timeline',
        done:false
      }

    ]

  };


  const monthTitle =
    document.getElementById('monthTitle');

  const calendarGrid =
    document.getElementById('calendarGrid');

  const scheduleListTitle =
    document.getElementById('scheduleListTitle');

  const itemCountEl =
    document.getElementById('itemCount');

  const scheduleListEl =
    document.getElementById('scheduleList');

  const prevMonthBtn =
    document.getElementById('prevMonth');

  const nextMonthBtn =
    document.getElementById('nextMonth');

  const tabs =
    document.querySelectorAll('.tab');

  const views =
    document.querySelectorAll('.view');

  const searchInput =
    document.getElementById('searchInput');

  const noteResults =
    document.getElementById('noteResults');

  const fabAdd =
    document.getElementById('fabAdd');

  const sheetBackdrop =
    document.getElementById('sheetBackdrop');

  const sheetSubtitle =
    document.getElementById('sheetSubtitle');

  const cancelBtn =
    document.getElementById('cancelBtn');

  const saveBtn =
    document.getElementById('saveBtn');

  const titleInput =
    document.getElementById('titleInput');

  const startTimeInput =
    document.getElementById('startTimeInput');

  const endTimeInput =
    document.getElementById('endTimeInput');

  const placeInput =
    document.getElementById('placeInput');

  const notesInput =
    document.getElementById('notesInput');

  const statusTime =
    document.getElementById('statusTime');


  function updateClock(){

    const now = new Date();

    let h =
      now.getHours() % 12;

    if (h === 0) {
      h = 12;
    }

    const m =
      String(now.getMinutes())
        .padStart(2,'0');

    statusTime.textContent =
      `${h}:${m}`;

  }


  updateClock();

  setInterval(
    updateClock,
    30000
  );


  function daysInMonth(year,month){

    return new Date(
      year,
      month + 1,
      0
    ).getDate();

  }


  function renderCalendar(){

    monthTitle.textContent =
      `${MONTH_NAMES[currentMonth]} ${currentYear}`;

    calendarGrid.innerHTML = '';


    const firstWeekday =
      new Date(
        currentYear,
        currentMonth,
        1
      ).getDay();

    const totalDays =
      daysInMonth(
        currentYear,
        currentMonth
      );


    const prevMonth =
      currentMonth === 0
        ? 11
        : currentMonth - 1;

    const prevYear =
      currentMonth === 0
        ? currentYear - 1
        : currentYear;

    const prevMonthDays =
      daysInMonth(
        prevYear,
        prevMonth
      );


    const cells = [];


    for (
      let i = firstWeekday - 1;
      i >= 0;
      i--
    ){

      cells.push({
        day:prevMonthDays - i,
        otherMonth:true,
        y:prevYear,
        m:prevMonth
      });

    }


    for (
      let d = 1;
      d <= totalDays;
      d++
    ){

      cells.push({
        day:d,
        otherMonth:false,
        y:currentYear,
        m:currentMonth
      });

    }


    const nextMonth =
      currentMonth === 11
        ? 0
        : currentMonth + 1;

    const nextYear =
      currentMonth === 11
        ? currentYear + 1
        : currentYear;

    let nextDay = 1;


    while (
      cells.length % 7 !== 0
    ){

      cells.push({
        day:nextDay++,
        otherMonth:true,
        y:nextYear,
        m:nextMonth
      });

    }


    cells.forEach(
      (cell,index) => {

        const isSunday =
          index % 7 === 0;

        const key =
          dateKey(
            cell.y,
            cell.m,
            cell.day
          );

        const hasEvents =
          !!events[key] &&
          events[key].length > 0;

        const isSelected =
          !cell.otherMonth &&
          cell.y === selectedDate.year &&
          cell.m === selectedDate.month &&
          cell.day === selectedDate.day;


        const cellEl =
          document.createElement('div');

        cellEl.className =
          'day-cell';


        if (cell.otherMonth) {
          cellEl.classList.add('other-month');
        }

        if (isSunday) {
          cellEl.classList.add('sunday');
        }

        if (isSelected) {
          cellEl.classList.add('selected');
        }

        if (
          hasEvents &&
          !cell.otherMonth
        ){
          cellEl.classList.add('today-dot');
        }


        cellEl.innerHTML =
          `<span class="day-num">${cell.day}</span>`;


        cellEl.addEventListener(
          'click',
          () => {

            selectedDate = {
              year:cell.y,
              month:cell.m,
              day:cell.day
            };

            if (cell.otherMonth){

              currentYear =
                cell.y;

              currentMonth =
                cell.m;

            }

            renderCalendar();
            renderScheduleList();

          }
        );


        calendarGrid.appendChild(
          cellEl
        );

      }
    );

  }


  function goToPrevMonth(){

    currentMonth--;

    if (currentMonth < 0){

      currentMonth = 11;
      currentYear--;

    }

    renderCalendar();

  }


  function goToNextMonth(){

    currentMonth++;

    if (currentMonth > 11){

      currentMonth = 0;
      currentYear++;

    }

    renderCalendar();

  }


  prevMonthBtn.addEventListener(
    'click',
    goToPrevMonth
  );

  nextMonthBtn.addEventListener(
    'click',
    goToNextMonth
  );


  function isToday(y,m,d){

    const today =
      new Date();

    return (
      today.getFullYear() === y &&
      today.getMonth() === m &&
      today.getDate() === d
    );

  }


  function formatSelectedDateLabel(){

    const {
      year,
      month,
      day
    } = selectedDate;

    const label =
      `${MONTH_NAMES_FULL[month]} ${day}, ${year}`;

    return isToday(
      year,
      month,
      day
    )
      ? "Today's Schedule"
      : `${label}'s Schedule`;

  }


  function renderScheduleList(){

    const key =
      dateKey(
        selectedDate.year,
        selectedDate.month,
        selectedDate.day
      );

    const list =
      events[key] || [];


    scheduleListTitle.textContent =
      formatSelectedDateLabel();

    itemCountEl.textContent =
      `${list.length} ${
        list.length === 1
          ? 'item'
          : 'items'
      }`;


    scheduleListEl.innerHTML = '';


    if (list.length === 0){

      scheduleListEl.innerHTML =
        '<div class="empty-state">No schedule for this day yet.</div>';

      return;

    }


    list.forEach(
      (event,index) => {

        const isLast =
          index === list.length - 1;

        const card =
          document.createElement('div');

        card.className =
          'schedule-card';


        card.innerHTML = `

          <div class="timeline">

            <div class="dot">
              ${selectedDate.day}
            </div>

            ${
              isLast
                ? ''
                : '<div class="line"></div>'
            }

          </div>


          <div class="card-body">

            <div class="card-top">

              <span class="card-title">
                ${escapeHtml(event.title)}
              </span>

              <span
                class="checkbox ${event.done ? 'checked' : ''}"
                data-id="${event.id}">
              </span>

            </div>


            <div class="card-row">

              <span class="label">
                Time
              </span>

              <span class="value">
                ${formatTimeRange(event)}
              </span>

            </div>


            ${
              event.place
                ? `<div class="card-row">
                     <span class="label">Place</span>
                     <span class="value">${escapeHtml(event.place)}</span>
                   </div>`
                : ''
            }


            ${
              event.notes
                ? `<div class="card-row">
                     <span class="label">Notes</span>
                     <span class="value">${escapeHtml(event.notes)}</span>
                   </div>`
                : ''
            }

          </div>

        `;


        scheduleListEl.appendChild(
          card
        );

      }
    );


    scheduleListEl
      .querySelectorAll('.checkbox')
      .forEach(
        checkbox => {

          checkbox.addEventListener(
            'click',
            () => {

              const id =
                checkbox.dataset.id;

              const event =
                list.find(
                  item =>
                    item.id === id
                );

              if (event){

                event.done =
                  !event.done;

                renderScheduleList();

              }

            }
          );

        }
      );

  }


  function formatTimeRange(event){

    if (
      event.start &&
      event.end
    ){

      return `${event.start} - ${event.end}`;

    }

    return (
      event.start ||
      event.end ||
      ''
    );

  }


  function escapeHtml(string){

    const div =
      document.createElement('div');

    div.textContent =
      string;

    return div.innerHTML;

  }


  tabs.forEach(
    tab => {

      tab.addEventListener(
        'click',
        () => {

          tabs.forEach(
            item =>
              item.classList.remove('active')
          );

          views.forEach(
            view =>
              view.classList.remove('active')
          );

          tab.classList.add('active');


          const target =
            tab.dataset.tab === 'schedule'
              ? 'scheduleView'
              : 'noteView';

          document
            .getElementById(target)
            .classList.add('active');


          if (
            tab.dataset.tab === 'note'
          ){

            renderNoteResults(
              searchInput.value
            );

          }

        }
      );

    }
  );


  function getAllEventsFlat(){

    const flat = [];


    Object.keys(events)
      .forEach(
        key => {

          const [y,m,d] =
            key
              .split('-')
              .map(Number);

          events[key]
            .forEach(
              event =>
                flat.push({
                  ...event,
                  y,
                  m,
                  d
                })
            );

        }
      );


    flat.sort(
      (a,b) =>
        new Date(
          b.y,
          b.m,
          b.d
        ) -
        new Date(
          a.y,
          a.m,
          a.d
        )
    );


    return flat;

  }


  function renderNoteResults(query){

    const q =
      (query || '')
        .trim()
        .toLowerCase();


    const flat =
      getAllEventsFlat()
        .filter(
          event =>
            !q ||
            event.title
              .toLowerCase()
              .includes(q) ||
            (event.notes || '')
              .toLowerCase()
              .includes(q)
        );


    noteResults.innerHTML = '';


    if (flat.length === 0){

      noteResults.innerHTML =
        '<div class="empty-state">No results found.</div>';

      return;

    }


    let lastKey = null;


    flat.forEach(
      event => {

        const key =
          dateKey(
            event.y,
            event.m,
            event.d
          );


        if (key !== lastKey){

          const label =
            document.createElement('div');

          label.className =
            'note-date-label';

          label.textContent =
            `${MONTH_NAMES_FULL[event.m].toUpperCase()} ${event.d}, ${event.y}`;

          noteResults.appendChild(
            label
          );

          lastKey =
            key;

        }


        const card =
          document.createElement('div');

        card.className =
          'note-card';


        card.innerHTML = `

          <div class="note-card-top">

            <span class="note-card-title">
              ${escapeHtml(event.title)}
            </span>

            <span class="note-card-time">
              ${formatTimeRange(event)}
            </span>

          </div>

          <div class="note-card-notes">
            ${escapeHtml(event.notes || '')}
          </div>

        `;


        noteResults.appendChild(
          card
        );

      }
    );

  }


  searchInput.addEventListener(
    'input',
    () =>
      renderNoteResults(
        searchInput.value
      )
  );


  function openSheet(){

    const {
      year,
      month,
      day
    } = selectedDate;


    sheetSubtitle.textContent =
      `For ${MONTH_NAMES_FULL[month]} ${day}, ${year}`;


    titleInput.value = '';
    startTimeInput.value = '09:00 AM';
    endTimeInput.value = '10:00 AM';
    placeInput.value = '';
    notesInput.value = '';


    sheetBackdrop
      .classList
      .add('open');


    setTimeout(
      () =>
        titleInput.focus(),
      300
    );

  }


  function closeSheet(){

    sheetBackdrop
      .classList
      .remove('open');

  }


  fabAdd.addEventListener(
    'click',
    openSheet
  );

  cancelBtn.addEventListener(
    'click',
    closeSheet
  );


  sheetBackdrop.addEventListener(
    'click',
    event => {

      if (
        event.target ===
        sheetBackdrop
      ){

        closeSheet();

      }

    }
  );


  saveBtn.addEventListener(
    'click',
    () => {

      const title =
        titleInput.value.trim();


      if (!title){

        titleInput.focus();

        titleInput.style.borderColor =
          '#e05555';

        return;

      }


      const {
        year,
        month,
        day
      } = selectedDate;


      const key =
        dateKey(
          year,
          month,
          day
        );


      if (!events[key]) {
        events[key] = [];
      }


      events[key].push({

        id:
          'e' + Date.now(),

        title:title,

        start:
          startTimeInput.value,

        end:
          endTimeInput.value,

        place:
          placeInput.value.trim(),

        notes:
          notesInput.value.trim(),

        done:false

      });


      closeSheet();

      renderCalendar();
      renderScheduleList();

    }
  );


  function cycleTime(input){

    const presets = [
      '08:00 AM',
      '09:00 AM',
      '10:00 AM',
      '11:00 AM',
      '12:00 PM',
      '01:00 PM',
      '02:00 PM',
      '03:00 PM',
      '04:00 PM',
      '05:00 PM',
      '06:00 PM'
    ];


    const index =
      presets.indexOf(
        input.value
      );


    input.value =
      presets[
        (index + 1) %
        presets.length
      ] || presets[0];

  }


  startTimeInput.addEventListener(
    'click',
    () =>
      cycleTime(
        startTimeInput
      )
  );

  endTimeInput.addEventListener(
    'click',
    () =>
      cycleTime(
        endTimeInput
      )
  );


  renderCalendar();
  renderScheduleList();

})();

How This Interactive Schedule Calendar App Works

The Interactive Schedule Calendar App is a mobile-style scheduling interface that combines a monthly calendar, daily event list, searchable notes, and an add-schedule sheet. The main .phone container is 360px wide and 640px high, with a dark gradient background, 40px rounded corners, and cyan and purple glow effects. Below 400px, the layout expands to the full viewport width and height and removes the rounded phone frame.

The calendar begins with August 2026 selected. JavaScript stores the current month and year separately, while selectedDate tracks August 31, 2026 as the initial active date. The events object stores schedules under keys generated in a year-month-day format. Two example events—Design Review and Coffee with Sarah—are assigned to August 31.

The calendar itself is generated dynamically by renderCalendar(). JavaScript calculates the first weekday and number of days in the current month, then adds dates from the previous and next months so every displayed row contains seven cells. Each generated .day-cell receives classes according to its state, including other-month, sunday, selected, and today-dot. In this implementation, today-dot is applied to current-month dates that contain events, despite the class name.

Clicking a date updates selectedDate and re-renders both the calendar and schedule list. Selecting a visible date from an adjacent month also changes currentMonth and currentYear. The previous and next buttons handle month navigation, including transitions between December and January.

renderScheduleList() looks up events for the selected date and builds schedule cards dynamically. Each card can display a title, time range, place, and notes. The circular checkbox changes an event’s done value between true and false, then re-renders the list. The checked state is represented visually with a cyan-to-purple gradient and checkmark.

The Note tab provides a second way to browse schedules. getAllEventsFlat() converts the date-keyed event object into a single array and sorts entries by date. As the user types, renderNoteResults() performs a case-insensitive search against event titles and notes using includes(). For more detail about this matching method, see the MDN String includes documentation.

The floating + button opens a bottom sheet for creating a schedule on the selected date. The title is required, while place and notes are optional. Start and end time fields are read-only; clicking either cycles through predefined hourly values from 8:00 AM through 6:00 PM. Saving pushes a new object into the in-memory events collection and immediately refreshes the calendar and schedule list.

The interface also displays a live status-bar clock. updateClock() reads the current time and formats it in 12-hour form, while setInterval() refreshes it every 30 seconds.

Customizing the Interactive Schedule Calendar App

The component defines its main visual settings with CSS custom properties, while its initial calendar data and time choices are controlled in JavaScript.

  • Change --cr-bg, --cr-phone, --cr-card, and --cr-field to customize the dark surfaces.
  • Edit --cr-cyan: #22d3ee and --cr-purple: #a855f7 to change the primary accent colors.
  • Adjust the .phone width of 360px, height of 640px, or 40px border radius to modify the device-style frame.
  • Change currentYear, currentMonth, and selectedDate to choose a different initial calendar view.
  • Edit the initial events object to provide different default schedules.
  • Modify the presets array inside cycleTime() to change the available start and end times.
  • Adjust the .35s bottom-sheet transition to change its opening speed.
  • Change the max-width:400px media query to control when the full-screen mobile layout activates.

Where to Use This Interactive Schedule Calendar App

This component is useful when a project needs a compact calendar interface with basic event creation, completion tracking, and search.

  • Personal daily planners and scheduling tools
  • Appointment interface prototypes
  • Student timetable and study-planning apps
  • Task calendars with date-based activities
  • Mobile dashboard concepts
  • Event and meeting organizer interfaces
  • Front-end JavaScript portfolio projects

For a more focused form interaction, the Interactive Lamp Login Reveal Form is another CodeRipple component that uses JavaScript state changes with a dark UI. In this calendar, newly created schedules exist only in the current JavaScript session because the code does not save them to local storage, a database, or an external service.

Frequently Asked Questions

Can users move between different months?

Yes. The previous and next navigation buttons update currentMonth. The JavaScript also adjusts currentYear automatically when navigation crosses from January to December or December to January.

Can users add new schedules?

Yes. Clicking the floating + button opens the New Schedule sheet for the currently selected date. A title is required, and the user can also provide a place and notes before saving the event.

Does the search check every schedule field?

No. The Note tab’s search specifically checks the event title and notes. Place and time values are displayed in other parts of the interface but are not included in the search condition.

Are newly added events saved permanently?

No. Events are stored in the JavaScript events object in memory. Reloading the page resets the component to its original event data because no persistent storage or backend is implemented.

The Interactive Schedule Calendar App demonstrates how calendar generation, date selection, event rendering, search, completion states, and event creation can be coordinated in one JavaScript-driven interface.

Scroll to Top