Interactive Schedule Calendar App
A responsive schedule calendar with month navigation, daily tasks, notes, search and an interactive add schedule form.
AUGUST 2026
August 31, 2026’s Schedule
2 itemsNew Schedule
For September 1, 2026
<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">
‹
</button>
<h1
class="month-title"
id="monthTitle">
AUGUST 2026
</h1>
<button
class="nav-btn"
id="nextMonth"
type="button"
aria-label="Next month">
›
</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>
: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;
}
}
(() => {
'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-fieldto customize the dark surfaces. - Edit
--cr-cyan: #22d3eeand--cr-purple: #a855f7to change the primary accent colors. - Adjust the
.phonewidth of 360px, height of 640px, or 40px border radius to modify the device-style frame. - Change
currentYear,currentMonth, andselectedDateto choose a different initial calendar view. - Edit the initial
eventsobject to provide different default schedules. - Modify the
presetsarray insidecycleTime()to change the available start and end times. - Adjust the
.35sbottom-sheet transition to change its opening speed. - Change the
max-width:400pxmedia 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.
