5 Pure CSS2 with JSPublished 2026-10-05Free · no sign-up
Seven calendar layouts: a month view built as a real table, a date picker with month navigation, a week schedule placed with CSS Grid, a check-in and check-out range picker, tear-off date badges for event lists, a styled native date input and an agenda list grouped by day.
A month grid built as a real table with day-name headers, so screen readers announce each date with its weekday. Today has aria-current set to date, days from other months are dimmed, and the grid uses table-layout: fixed for even columns.
Days are buttons in a grid; clicking one selects it, marks it with aria-pressed and writes the full date into an output. Previous and next month buttons are named in full, each day button's name includes the weekday, and the month heading is a live region.
var w = document.querySelector('.cal2'), g = w.querySelector('.g'), h = w.querySelector('h3'), out = w.querySelector('output'), d = new Date(2026, 9, 1);
function draw() {
g.innerHTML = ''; h.textContent = d.toLocaleDateString('en-GB', { month: 'long', year: 'numeric' });
var lead = (new Date(d.getFullYear(), d.getMonth(), 1).getDay() + 6) % 7, days = new Date(d.getFullYear(), d.getMonth() + 1, 0).getDate();
for (var i = 0; i < lead; i++) g.appendChild(document.createElement('span'));
for (var k = 1; k <= days; k++) { var b = document.createElement('button'); b.type = 'button'; b.textContent = k; b.setAttribute('aria-pressed', 'false');
b.setAttribute('aria-label', new Date(d.getFullYear(), d.getMonth(), k).toLocaleDateString('en-GB', { weekday: 'long', day: 'numeric', month: 'long' })); g.appendChild(b); }
}
g.onclick = function (e) { var b = e.target.closest('button'); if (!b) return; g.querySelectorAll('[aria-pressed=true]').forEach(function (x) { x.setAttribute('aria-pressed', 'false'); }); b.setAttribute('aria-pressed', 'true'); out.textContent = b.getAttribute('aria-label'); };
w.querySelector('.p').onclick = function () { d.setMonth(d.getMonth() - 1); draw(); };
w.querySelector('.n').onclick = function () { d.setMonth(d.getMonth() + 1); draw(); };
draw();
03. Week Schedule on CSS Grid
CalendarSchedule
A five-day week with time rows and event blocks placed by grid line numbers: an event from 10:00 to 12:00 spans grid-row 3 / 5. Each event has its time written out, so the schedule still makes sense when read in order.
Picking a check-in and a check-out date highlights every day between them as one connected band. Classes for start, middle and end round the right corners, the two chosen days are pressed buttons, and the range is written out as text under the grid.
View code
<div class="cal4"><p class="m">November 2026</p><div class="g"></div><p class="out" aria-live="polite">Choose your check-in date</p></div>
var g = document.querySelector('.cal4 .g'), out = document.querySelector('.cal4 .out'), a = null, b = null;
for (var i = 0; i < 6; i++) g.appendChild(document.createElement('span'));
for (var k = 1; k <= 30; k++) { var x = document.createElement('button'); x.type = 'button'; x.textContent = k; x.dataset.d = k; x.setAttribute('aria-label', k + ' November'); g.appendChild(x); }
g.onclick = function (e) {
var t = e.target.closest('button'); if (!t) return; var d = +t.dataset.d;
if (a === null || b !== null || d <= a) { a = d; b = null; } else { b = d; }
g.querySelectorAll('button').forEach(function (x) { var v = +x.dataset.d, c = []; x.removeAttribute('aria-pressed');
if (v === a) c.push('start'); if (v === (b === null ? a : b)) c.push('end'); if (b !== null && v > a && v < b) c.push('mid');
if (v === a || v === b) x.setAttribute('aria-pressed', 'true'); x.className = c.join(' '); });
out.textContent = b === null ? 'Check-in ' + a + ' November. Now choose check-out.' : a + ' to ' + b + ' November, ' + (b - a) + (b - a === 1 ? ' night' : ' nights');
};
05. Mini Date Badges for Events
CalendarBadge
The small tear-off calendar icon used on event listings: month on a coloured strip, day number large below it. Each badge is a time element with a full datetime, so the date is machine readable for browsers and search engines.
Often the best date picker is the browser's own: type="date" gives a localised, accessible picker on every device. This demo styles the field to match the page, limits it with min and max, and adds a hint about delivery days.
An agenda groups events under day headings, the most readable calendar view on phones. Day headings are h3 elements and each day's events are a list, so people can jump between days with a screen reader's heading shortcuts.
A month view has rows of weeks and columns of weekdays, so a <table> with column headers lets screen readers announce the weekday of every date. Mark today with aria-current="date".
Consider the native picker
<input type="date"> gives every visitor a localised, accessible picker that suits their device. Build a custom one only when you need ranges, prices per day or a calendar that stays open.
Events on CSS Grid
A schedule is a grid of days and time slots. Give each event its column and row lines (grid-row: 3 / 5 for two hours) and the layout handles overlaps and spacing for you.
Dates in machine-readable form
Wrap dates in <time datetime="2026-11-03">. Browsers, assistive technology and search engines can read the real date, whatever format you display.
Frequently asked questions
Which day should the week start on?
It depends on the country: Monday in most of Europe, Sunday in the US. The picker's code shows where to change it.
Do the pickers handle time zones?
They work in the visitor's local time. Convert to UTC on the server when storing bookings.
Can I style the native date picker popup?
Only the field itself. The popup is drawn by the browser and cannot be styled.