Navigable JavaScript Calendar with Month and Year Controls

A navigable JavaScript calendar keeps a displayed year and month as state, renders that month, and changes the state when the user chooses Previous/Next Month or Previous/Next Year. Selecting a day can then copy a predictable date value such as YYYY-MM-DD into an input.

let viewYear = 2026;
let viewMonth = 8; // September: months are 0-11

function showNextMonth() {
  const next = new Date(viewYear, viewMonth + 1, 1);
  viewYear = next.getFullYear();
  viewMonth = next.getMonth();
}

This continues the earlier monthly calendar and calendar-to-input projects by adding navigation. JavaScript normalizes month overflow automatically, so moving from December to the next month also advances the year.

Keep the Displayed Month and Year as State Top ↑

The historical version passed chm (change month) and chy (change year) into the calendar function. That idea is valid, but keeping the current display state in named variables makes the flow easier to follow and avoids accidental globals.

const today = new Date();
let viewYear = today.getFullYear();
let viewMonth = today.getMonth();

getMonth() is zero-based: January is 0 and December is 11. See JavaScript month values for details.

Constructing a date on day 1 avoids end-of-month surprises. A month index of 12 becomes January of the next year; -1 becomes December of the previous year.

function changeMonth(amount) {
  const next = new Date(viewYear, viewMonth + amount, 1);
  viewYear = next.getFullYear();
  viewMonth = next.getMonth();
}

function changeYear(amount) {
  viewYear += amount;
}

This is safer than applying setMonth() to a date currently on the 29th, 30th, or 31st because month overflow can otherwise move into a later month.

Calculate the First Weekday and Last Date Top ↑

Two Date objects provide the values required for a conventional 6-row calendar grid. getDay() returns 0-6 for Sunday-Saturday, while day 0 of the following month gives the last date of the current month.

const firstDay = (new Date(viewYear, viewMonth, 1)).getDay();
const lastDate = (new Date(viewYear, viewMonth + 1, 0)).getDate();

The original project used the same core calculations; the modernization changes how the UI is created, not the calendar arithmetic.

Return a Selected Date to the Input Top ↑

For form values, prefer an unambiguous format. Numeric MM/DD/YYYY can be misread in international contexts, while YYYY-MM-DD matches the value format used by native date inputs.

const pad = value => String(value).padStart(2, "0");

function formatDate(year, monthIndex, day) {
  return `${year}-${pad(monthIndex + 1)}-${pad(day)}`;
}

input.value = formatDate(viewYear, viewMonth, selectedDay);

Build Navigation and Day Controls with the DOM Top ↑

The old project assembled long HTML strings containing inline onclick handlers. For new code, create real buttons and attach events with addEventListener(). Buttons are also a better semantic fit because month navigation and day selection perform actions rather than navigating to another URL.

const nextButton = document.createElement("button");
nextButton.type = "button";
nextButton.setAttribute("aria-label", "Next month");
nextButton.textContent = "›";
nextButton.addEventListener("click", () => {
  changeMonth(1);
  renderCalendar();
});

Use the Same Logic for Multiple Independent Calendars Top ↑

Two independent inputs do not need two copies of the calendar code. Pass or store the target input and panel for each picker. The original variables bt and sub served this identification purpose; modern code can express it with data-* attributes.

<button type="button"
        data-calendar-trigger
        data-input="t1"
        data-panel="calendar1">
  Choose first date
</button>

Demo: two independent calendar pickers

Accessibility and Keyboard Behavior Top ↑

Use buttons for every actionable control, label the text field, expose open/closed state with aria-expanded, connect the trigger to the popup with aria-controls, and let Escape close the calendar. Disabled dates should use the native disabled state instead of color alone.

trigger.setAttribute("aria-expanded", String(opening));

document.addEventListener("keydown", event => {
  if (event.key === "Escape") closeCalendar();
});

Integrate the Calendar into an Existing Form Top ↑

Load the JavaScript file with defer, use a normal labeled input, and keep the popup next to the trigger in the DOM. Do not rely on a clickable image as the only control.

<script src="date-project-calendar4-demo.js" defer></script>

<label for="t1">Selected date</label>
<input id="t1" name="t1" type="text" readonly>
<button id="calendarTrigger" type="button" aria-controls="sub" aria-expanded="false">Choose date</button>
<div id="sub" hidden></div>

If your project only needs a date and does not require a custom calendar UI, consider native <input type="date"> first. A custom picker is justified when you need navigation behavior, constrained ranges, custom presentation, or teaching control over the rendering process.

Demo and Downloadable Examples Top ↑

Demo: navigable calendar with date selection

Download the navigable JavaScript calendar example

The next example extends the same approach to linked start and end date calendars, where the valid dates in one picker depend on the value selected in the other.

Date Reference All Date Projects




Subscribe to our YouTube Channel here



plus2net.com










✖
We use cookies to improve your browsing experience. . Learn more
HTML MySQL PHP JavaScript ASP Photoshop Articles Contact us
© 2000-2026 plus2net.com All rights reserved worldwide Privacy Policy Disclaimer