Most pages that ask for a calendar only need a date field, and the browser already ships one. The native date input has been Baseline widely available since April 2021, according to MDN Web Docs, and it submits the same yyyy-mm-dd value in every locale.

A full month grid with navigation and events is a different story. That one you build yourself or pull in.

How Can You Build an HTML Calendar?

See the Pen
Calendar – CSS only
by Álvaro (@alvarotrigo)
on CodePen.

Everything here renders from native elements, CSS and a little JavaScript, so no external calendar application gets involved. What changes from method to method is how much the browser does for you. With the native date input it does nearly all of it. A static table gives you markup and nothing else. Past that you’re writing your own CSS Grid and JavaScript, or handing the job to a library such as FullCalendar or Flatpickr. The first two need nothing beyond plain HTML.

MethodJavaScriptEffortBest for
Native date inputNoOne lineForm fields, booking dates
Static HTML tableNoLowOne fixed month, printed schedules
CSS Grid with JavaScriptYesMediumMonth view with navigation
Library or widgetYesFast setup, slow to restyleEvent calendars, date ranges

Pick the simplest method that meets the requirement. A date input beats a custom grid whenever the visitor only needs to choose one date.

Reaching for a library first is the usual mistake. It adds weight to a page that a single input element would have served.

The native date input needs no JavaScript, and the browser handles the popup and keyboard entry. The downside is that the picker looks different in each browser, and it can’t show event markers.

A static table works everywhere without a script and prints cleanly. You get one table per month and no navigation, and the dates are typed by hand or generated on the server.

A CSS Grid with JavaScript hands you full control over markup, layout and behavior, and any month on demand. Date logic and localization become your job, and so does accessibility, which is a lot to own.

A library comes with tested date logic and locale support, plus event APIs. The cost is extra page weight and style overrides, along with upgrade work down the line.

Flatpickr covers date picking, while a Bootstrap datepicker plugin suits projects already built on that framework. Event-heavy calendars are where a full library earns its weight.

Adding a Native Date Picker With the Date Input Type

See the Pen
Date and Time Picker
by Jarom Vogel (@jaromvogel)
on CodePen.

The markup is one input with type=”date” and a label. The browser supplies the popup and the keyboard handling, and it shows dates in the visitor’s locale.

Whatever the field displays, the submitted value is always yyyy-mm-dd (the ISO 8601 date format), according to MDN Web Docs. A US visitor sees the month first and a German visitor sees the day first. Either way the server receives the same string.

Always attach a label element. That is the baseline of accessible forms, and the date input has no visible text of its own.

Date, Month, Week and Datetime-Local Inputs

Input typeValue formatPicks
date2026-10-12One day
month2026-10One month and year
week2026-W42One week and year
datetime-local2026-10-12T14:30Day and time, no time zone

Billing periods and timesheets are where month and week pickers make sense. Bookings usually call for date or datetime-local.

Min, Max and Step Attributes

The min attribute sets the earliest accepted date and max sets the latest. Step controls the granularity in days, with a default of 1.

Dates outside the min and max range are disabled in the picker, and the form shows an error if someone submits one anyway (MDN Web Docs). Set step to 7 with a min that falls on a Saturday and only Saturdays can be selected.

In practice the bigger limit is that no attribute can block individual dates, holidays for example.

Browser Differences You Will See

caniuse counts 97.24% of global browser usage as supporting the date and time input types, 90.36% fully and 6.88% partially (StatCounter data for September 2026).

Partial support is flagged on desktop Safari and desktop Firefox, plus Firefox for Android. Safari on iOS reaches full support from version 18.2.

The popup design depends on the browser and the operating system. caniuse groups all 5 types in one row, so test month and week separately. If you want decoration such as validity icons, put it on a sibling element, because Chrome places generated content inside the control.

The date input itself has been Baseline widely available since April 2021, per MDN. A short round of cross-browser compatibility testing on iOS Safari and desktop Firefox catches most surprises.

Marking Up a Month Grid With the HTML Table Element

See the Pen
Simple Calendar
by BrainUP (@jpag82)
on CodePen.

A static calendar is a single table. It has a caption naming the month, a header row of weekday names and a body of week rows, and nothing needs a script to display.

The caption holds the month and year, which also titles the grid for assistive technology. The header row takes 7 th cells with scope=”col”, one per weekday. The tbody then gets 6 tr rows of 7 td cells, and each day number sits inside a time element with a datetime attribute such as 2026-10-12.

Six rows are needed because a 31-day month that starts on a Saturday spans six weeks in a Sunday-first layout. The html-calendar Python package documentation says the same thing, that every table it generates has 6 rows, even when the last one is empty.

Cells before the 1st and after the last day stay as empty td elements. A class on them lets CSS grey out the gap.

MDN describes the datetime attribute as a machine-readable date that supports features such as reminders. Day cells carry the yyyy-mm-dd form.

Header cells with scope attributes and a caption are the building blocks of accessible tables, and a calendar grid needs both.

This approach suits a printed schedule or a page where scripts are blocked, and it’s fine for a single fixed month such as an event date. It falls apart once you want month navigation, since each month is another table and a year takes 12. Highlighting today doesn’t work either, unless the server or a script marks the cell.

The table only covers the markup, so the dates have to come from JavaScript.

How to Generate a Calendar Month With JavaScript

Generating a month takes two numbers, the weekday of the 1st and the number of days in the month. The rest is a loop that fills rows of 7 columns.

The Date object in plain JavaScript returns both values, so no library is required.

  1. Store the target year and month in two variables, defaulting to today.
  2. Get the weekday of the 1st with new Date(year, month, 1).getDay().
  3. Get the day count with new Date(year, month + 1, 0).getDate().
  4. Render the weekday header cells in the order the calendar starts its week.
  5. Render empty cells up to the offset, then days 1 through the day count, then empty cells to finish the last row.
  6. Wire the previous and next buttons to change the month and re-render, rolling December into January of the next year.

Date Object Pitfalls

MDN documents getMonth() as a zero-based value from 0 to 11, so October is 9.

Passing 0 as the day, as in new Date(year, month + 1, 0), returns the last day of the month, and leap years take care of themselves (February 2028 returns 29). Rollover works in your favor too. new Date(2026, 12, 1) lands on January 1, 2027, so month navigation arithmetic is safe.

Weeks are where people slip. getDay() returns 0 for Sunday, so a Monday-first grid uses the offset (getDay() + 6) % 7.

Parsing bites just as often. Build dates from numbers, not from yyyy-mm-dd strings, because date-only strings parse as UTC and can shift the day in time zones behind it.

Most broken calendars trace back to one of these four.

Localized Names With Intl.DateTimeFormat

A hard-coded month array works for one language. Intl.DateTimeFormat, Baseline widely available since September 2017 (MDN Web Docs), returns names for any locale.

With month set to “long”, the locale en-US gives February and de-DE gives Februar. The same call with weekday set to “short” fills the header row.

Month and weekday names follow the locale, but the first day of the week doesn’t, so set it explicitly.

Styling an HTML Calendar With CSS Grid

See the Pen
Online Tutorials / Calendar UI Design With CSS Grid | Pure Html CSS UI Design
by mrnobody (@corvus-007)
on CodePen.

A 7-column CSS Grid puts the weekday headers and the day cells on the same tracks, so one rule lays out the whole month: grid-template-columns: repeat(7, minmax(0, 1fr)).

Use minmax(0, 1fr) instead of a bare 1fr. A plain 1fr column will not shrink below the width of its content, so one long event label can push the grid past the edge of the screen.

CSS Flexbox handles a single week strip well. A full month needs rows and columns together, which is the job Grid was built for.

Highlighting Today and Weekends

For today, have JavaScript add a class such as “today” to the cell whose date matches the current day.

Weekends can stay in CSS. In a Sunday-first grid, nth-child(7n+1) and nth-child(7n) hit Sunday and Saturday, and in a Monday-first grid nth-child(7n+6) and nth-child(7n) hit Saturday and Sunday. The leading and trailing cells from neighboring months get a muted color and no hover state.

The selectors count children of the grid container, so they assume the 7 weekday headers sit in that same container.

Tinted backgrounds still need enough color contrast against the day number. Test the highlighted cell, not just the plain one.

Keeping Cells Usable on Narrow Screens

See the Pen
Calendar mobile app UI
by Eliza Rajbhandari (@eliza-rjb)
on CodePen.

WCAG 2.2 success criterion 2.5.8 (Level AA) sets the minimum pointer target at 24 by 24 CSS pixels, according to W3C’s Understanding documentation.

A 320 px screen with 16 px side gutters leaves 288 px, about 41 px per column before gaps. The day cells clear the minimum, so the weak spot is usually the previous and next buttons.

To fit the space, abbreviate weekday names to one or two letters and swap event text for a dot or a count. Below a certain breakpoint, drop the grid and show a plain list of the month’s events instead.

The breakpoint switch is a job for media queries, which can hide the grid and reveal the list.

W3C also lists an exemption worth knowing. The calendar inside a native date input counts as a user agent control, so it is excepted as long as the author leaves its default size alone.

How Do You Make an HTML Calendar Accessible?

Use the native date input where it fits. A custom grid has to rebuild keyboard movement and focus handling, plus every screen reader label, from scratch.

The W3C ARIA Authoring Practices Guide (APG) publishes a date picker dialog example built on its grid pattern, last updated 12 August 2025. W3C warns that the example is not production code and that it needs testing with assistive technologies.

KeyFocus moves to
Left, RightPrevious day, next day
Up, DownSame weekday in the previous or next week
Home, EndFirst or last day of the current week
Page Up, Page DownSame day number in the previous or next month
Shift + Page Up, Shift + Page DownSame day number in the previous or next year

When the target month has no matching day number (Page Up from March 31, for example), focus lands on the last day of that month. The APG spells this case out, and it is easy to miss in a custom grid.

The example layers a handful of ARIA roles and states on top of the table markup. The table gets role=”grid” and is named through aria-labelledby. A roving tabindex gives one day cell tabindex=”0″ and the rest -1, so Tab enters the grid once. Two-letter weekday headers carry the complete name in the abbr attribute, and aria-live=”polite” on the month heading announces month changes.

Today and selected are separate states. MDN lists aria-current=”date” for the current date in a calendar, while the APG example marks only the chosen date with aria-selected and leaves aria-current out of its attribute table.

MDN adds two rules. Only one element in a set should be marked current, and aria-current should never stand in for aria-selected on a gridcell. Use both attributes, on different cells.

Libraries copy the keyboard map but not every detail. The react-datepicker README lists the same arrow, Page Up, Page Down, Home and End behavior, yet it closes the calendar on Tab, where the APG dialog keeps Tab moving inside the dialog.

Before shipping, work through a checklist for web accessibility and run a pass with a screen reader.

Adding Events, Google Calendar Embeds and .ics Files to an HTML Calendar

See the Pen
Multiple months calendar CSS
by Álvaro (@alvarotrigo)
on CodePen.

Events attach to a day cell through a date key. Store them as a JSON array whose dates use the same yyyy-mm-dd string as each time element, then render a marker in the matching cell.

Event Markers From JSON Data

See the Pen
Calendar Plan – Tasks Events App #DailyPractice
by Ahmad Nasr (@ahmadnasr)
on CodePen.

Each event is one object with a date and a title, plus an optional url. Show a dot or a count per day, and put the full list under the grid on small screens. Give the marker text for screen readers, such as “2 events”.

Fetching the array through an asynchronous Ajax request keeps the page itself static and lets each month load only its own events.

The same fields feed Event structured data on each event’s own page. Google’s Search Central documentation requires name, startDate and a location with an address.

Google supports only pages that focus on a single event, so a calendar page listing many events should not carry the markup.

The same documentation cites Eventbrite, which saw a 100% increase in its typical year-over-year growth of traffic from Google Search through the event experience.

Embedding Google Calendar

Google’s embed customizer asks for a width and a height of at least 500 each. Getting to it takes a few clicks, and only on desktop, because the mobile app cannot produce the code.

  1. Open Settings.
  2. Pick the calendar’s name.
  3. Choose Integrate calendar.
  4. Select Customize under Embed code.

The calendar shows only to people it is shared with, so make it public for anonymous visitors. With the navigation buttons switched off, visitors can change months only through keyboard shortcuts. An Add to Google Calendar link sits at the bottom left of the embed.

The embed is an iframe, a separate document, so your own CSS cannot restyle its cells. That is the price of zero date logic.

WordPress sites place the code differently, and a separate guide covers adding an events calendar to a WordPress page.

Offering an iCalendar Download

iCalendar, defined in RFC 5545 (IETF, September 2009), is the file format calendar apps import to exchange events independent of any calendar service.

Date formExampleMeaning
FloatingDTSTART:19970714T133000Same wall-clock time in every time zone
UTCDTSTART:19970714T173000ZOne absolute moment
Named zoneDTSTART;TZID=America/New_York:19970714T133000Local time bound to a time zone definition

A named zone needs a matching VTIMEZONE component inside the file. Leave it out and different apps read the local time differently.

All-day events use VALUE=DATE with the date written as 8 digits and no hyphens (19970714), unlike the hyphenated yyyy-mm-dd that HTML uses.

Content lines end with a CRLF sequence, and long lines are folded. Generate the file on the server and serve it as a download from an “Add to calendar” link.

When Does a Hand-Built HTML Calendar Stop Working?

It stops working when events need recurrence rules, time zone conversion, drag and drop, or several views. Below that line, table cells and a few dozen lines of JavaScript hold up fine.

Failure Modes of a Custom Calendar

Recurring events are the first wall. RFC 5545 defines recurrence rules with 14 rule parts, and only FREQ is required. “Last working day of the month” takes FREQ=MONTHLY;BYDAY=MO,TU,WE,TH,FR;BYSETPOS=-1, and impossible dates such as February 30 must be skipped.

Time zones come next. The react-datepicker README says its native Date objects display in the visitor’s local zone and that the library has no conversion tools. It points to date-fns-tz for that job.

Interaction is another gap. FullCalendar documents event dragging and resizing, popovers and virtual rendering as separate features, and a hand-built grid has none of them.

Then there’s upkeep. Browser updates and new locales land on you, and so does every accessibility regression.

If none of the four applies, stay hand-built.

Library Options and Their Fit

LibraryBest fitNotes
FullCalendarEvent calendars with month, week and list viewsv7.1.2 released October 12, 2026; Timeline and resource views are Premium
FlatpickrDate and time picking on form fieldsNo dependencies, MIT license
react-datepickerDate picking in React appsNative Date objects, date-fns for locales, MIT license
jQuery UI DatepickerLegacy jQuery projectsTied to a text input; inline, multi-month and range demos
PikadayNothing newArchived August 11, 2025

Pikaday no longer belongs on the shortlist, even though older tutorials still recommend it. Its maintainers archived the repository, noting that it started before input type date was supported and is probably not the right choice today.

Date math inside a hand-built grid does not need a full calendar library. Day.js weighs 2kB, with a largely Moment.js-compatible API and immutable objects, per the Day.js site.

date-fns is the other common pick. react-datepicker switched to it from Moment.js at version 2.0.0, according to its README, to cut package size.

HTML calendar examples

Night Vibes CSS Calendar

See the Pen
CSS – Winter Animation Calendar
by Shraddha (@5hraddha)
on CodePen.

Roll the Days: Dice Calendar

See the Pen
Dice Calendar
by Jakob Eriksen (@jakob-e)
on CodePen.

Masterpiece Calendar Card4

See the Pen
Day 11 – Calendar Card
by Arnelle Balane (@arnellebalane)
on CodePen.

Go Big or Go Home CSS Calendar

See the Pen
Full-screen CSS calendar with flexbox
by Álvaro (@alvarotrigo)
on CodePen.

Special-Purpose Flight Finder Calendar

See the Pen
Example React Calendar App
by Ruben Martinez (@rubencodes)
on CodePen.

Blog Date Calendar Badge

See the Pen
Calendar Icon in CSS
by Álvaro (@alvarotrigo)
on CodePen.

Smooth Moves: Calendar

See the Pen
Fluent Design: Calendar
by Antoinette Janus (@internette)
on CodePen.

The Yearly Overview

See the Pen
year calendar
by Álvaro (@alvarotrigo)
on CodePen.

Flip it Good Calendar

See the Pen
Parallax flipping calendar
by Andreas Pihl Jrgensen (@andreas_pihl)
on CodePen.

Simply Effective

See the Pen
Daily CSS Images :: 09 Calendar
by Chris McNally (@chrismcnally)
on CodePen.

Seasons in Motion

See the Pen
SVG season calendar with animated header
by Joke Gysen (@keukenrolletje)
on CodePen.

Splash of Colour

See the Pen
CSS-only Colorful Calendar Concept
by Álvaro (@alvarotrigo)
on CodePen.

Endless Days

See the Pen
Infinite Calendar
by Tadaima (@tadaima)
on CodePen.

Going in Circles

See the Pen
Round Calendar
by Marco Biedermann (@marcobiedermann)
on CodePen.

Shades of Two

See the Pen
Duotone calendar
by yumeeeei (@yumeeeei)
on CodePen.

Columns All Day

See the Pen
CSS-Only Calendar Layout [CSS Grid]
by Álvaro (@alvarotrigo)
on CodePen.

Clock n’ Calendar Combo

See the Pen
Circular Calendar Display
by Matthew Juggins (@mattjuggins)
on CodePen.

The Coder’s Festive Days

See the Pen
A Coder’s Advent Calendar
by Jesse Couch (@designcouch)
on CodePen.

Pocket-Sized Planner

See the Pen
Calendar Widget
by Álvaro (@alvarotrigo)
on CodePen.

The Spotlight Stealer

See the Pen
Calendar Mockup
by Dan Couper (@DanielCouper)
on CodePen.

Boldness Overload by Sam

See the Pen
Untitled
by Sam Breed (@wookiehangover)
on CodePen.

Your Everyday Guide

See the Pen
Daily CSS Images | 09 | Calendar
by Álvaro (@alvarotrigo)
on CodePen.

Flip it Real Good

See the Pen
Calendar Flip Animation
by Gabriel Colombo (@gabrielcolombo)
on CodePen.

Blue-tiful Days

See the Pen
#DailyCSSImages 9 – Calendar
by Davide Francesco Merico (@NeckersBOX)
on CodePen.

Crafted with Pizzazz

See the Pen
Wim Crouwel’s Calendar (CSS Grid)
by ▥ (@droom)
on CodePen.

That Oomph Factor

See the Pen
Calendario
by Romswell Roswell Parian Paucar (@romswellparian)
on CodePen.

HTML Calendar FAQ

Is there a calendar element in HTML?

No. HTML has no calendar tag.

The closest tools are the input element with type date, month or week, which opens a browser picker, and the table element, which lays out a static month grid.

Can a native date input block weekends or specific dates?

Not through attributes. The min, max and step attributes limit the range and spacing, but none of them can exclude holidays or weekends.

Check the chosen date in a change listener and call setCustomValidity with an error message. Repeat the check on the server, since MDN warns that client-side validation is not a substitute.

Can native HTML pick a date range?

Not with a single control. Use two date inputs, one for the start and one for the end.

A short script sets the end input’s min attribute to the start value whenever it changes, so the browser rejects an end date that falls before the start.

How do you print an HTML calendar?

Add a print stylesheet that hides the previous and next buttons and gives every cell a visible border, with a fixed row height.

Browsers skip background colors by default, so set print-color-adjust to exact when weekend tints must appear on paper.

How do you add week numbers to an HTML calendar?

Add a leading column, change the grid rule to repeat(8, minmax(0, 1fr)), and compute the ISO week for each row.

RFC 5545 defines week number one as the first week with at least 4 days in the new year. ISO 8601 weeks start on Monday, so Monday-first rows match that numbering.

How do you pre-fill a date input with today’s date?

Build the yyyy-mm-dd string from the local year, month and day, then assign it to the input’s value property.

Avoid valueAsDate with new Date(). It works in UTC, so near midnight it can fill in the neighboring day.

Where can you find free HTML calendar templates?

See the Pen
CSS calendar – Responsive
by Álvaro (@alvarotrigo)
on CodePen.

Tutorial sites such as GeeksforGeeks publish copy-paste table and CSS calendars. HCAL, a form-based generator, returns a month table that you then edit by hand.

Check each one for a caption and scope attributes on the header cells before reusing it.

What to Test Before Launching an HTML Calendar

Before launch, check keyboard and screen reader use, locale output, time zone handling and the browsers you target. The order matters.

Start with the keyboard and screen reader on the bare markup, since markup defects cost least to fix before styling and locale data exist. Then run locale and time zone checks with real event data, and finish with a browser and device matrix down to 320 px.

Embeds need a separate check. Google’s customizer sets a minimum of 500 for width and height, which exceeds the 288 px content width of a 320 px screen, so a pasted embed needs a width rule.

The trade-off is manual effort that a maintained library’s test suite would absorb. This position was verified on October 14, 2026, and the checks should run again whenever a calendar library ships a new major version, as FullCalendar did with v7.

Bogdan Sandu
Latest posts by Bogdan Sandu (see all)