Article

Build an Internationalized Calendar with Vanilla JavaScript

Calendars look simple. Seven columns, five or six rows, a few numbers inside. But behind that unassuming grid lies a tangle of complexity—timezones that shift without warning, date formats that change across borders, and accessibility concerns that trip up even seasoned developers.

A recent tutorial on CSS-Tricks tackles this head-on. It walks through building a fully internationalized calendar using vanilla JavaScript, leaning on the browser's built-in Intl suite instead of Moment.js or date-fns. No dependencies, no framework overhead.

  • The tutorial demonstrates a custom calendar component named "Kal-El"
  • It uses the Intl APIs for locale-aware date handling
  • Semantic HTML with <time> elements ensures screen reader compatibility
  • The approach handles locale-specific week start days automatically
  • Zero external libraries required

Why Calendars Are Harder Than They Look

Most developers reach for a library the moment they need a date picker or event calendar. There's a good reason for that. The Date() object in JavaScript is notoriously quirky—months are zero-indexed, years sometimes off by one, and timezone handling requires careful attention.

Before the Intl API matured, internationalizing a calendar meant maintaining sprawling lookup tables. You'd need arrays mapping every locale to its first day of week, weekend days, and date format preferences. Updating that data was tedious, and mistakes were easy.

The Intl API changes the equation. Intl.Locale.weekInfo returns exactly what you need: firstDay tells you whether Sunday or Monday starts the week for a given locale. Weekend tells you which days count as weekend days. For en-US, that's Saturday and Sunday. For Brunei (ms-BN), it's Friday and Sunday.

This information used to require external data or hardcoded objects. Now it's a single API call.

Building the Component

The tutorial's approach centers on a configuration object that merges user settings with sensible defaults. Pass in a locale and initial date, and the component handles the rest.

“`javascript

Const config = Object.assign(

{ locale: 'en-US', date: today },

Settings

);

“`

The component checks the <html> element for a lang attribute and falls back to en-US if nothing is specified. This gives you automatic internationalization—the same component renders differently depending on the user's locale settings.

From there, the calendar calculates which days belong in which weeks, applies locale-appropriate formatting to month names, and renders semantic HTML that assistive technologies can parse correctly.

Semantic Markup and Accessibility

Screen readers need proper structure to announce dates meaningfully. The tutorial emphasizes semantic HTML from the start, wrapping the calendar in a custom <kal-el> element (since no native <calendar> tag exists) and using <time> elements with proper datetime attributes.

This matters in practice. A visually styled date like "Jan 15, 2024" might display one way, but <time datetime="2024-01-15"> tells screen readers exactly what's being communicated. Search engines benefit too—structured date data helps with indexing and rich snippets.

Our Analysis: When This Approach Makes Sense

The vanilla JavaScript approach isn't always the right call. Here's the honest breakdown.

You should build from scratch if you're learning, building a simple date picker for a form, creating a basic event listing with minimal complexity, or working in an environment where bundle size matters and you can't add dependencies.

You should reach for a library like FullCalendar or jQuery UI if you need drag-and-drop scheduling, recurring event logic, integration with Google Calendar or Outlook, or complex edge cases that would require significant custom code.

The vanilla approach trades breadth for simplicity. You get clean, maintainable code that handles the fundamentals well. But anything beyond basic display and selection requires reinventing wheels that libraries have already solved.

A Practical Scenario

Imagine you're building a booking widget for a local gym. Users need to select available time slots in their own locale.

With the vanilla approach, a user in Germany automatically sees weeks starting Monday. A US user sees weeks starting Sunday. Month names format according to their language settings. No configuration per user, no conditional logic—just correct behavior by default.

This matters more than it first appears. Booking systems that assume US conventions frustrate international users. Calendar components that adapt automatically feel native, even when they're built once and deployed everywhere.

What to Watch Next

The Intl API continues expanding, but its foundations are stable in modern browsers. If you want to future-proof further, keep an eye on Temporal—a proposed new Date/Time API that aims to replace the legacy Date object with a cleaner, more intuitive interface.

For now, the Intl API handles most internationalization needs without external dependencies. The vanilla JS calendar demonstrated on CSS-Tricks proves the point: you can build something solid without building everything yourself.

Bottom Line

Building a calendar with vanilla JavaScript and the Intl API is viable for projects that don't need heavy scheduling features. The approach gives you automatic internationalization, good accessibility, and zero dependencies. For anything more complex, libraries still earn their weight. Know your project's scope before deciding which path to take.

Key takeaways

  • The Intl API provides locale-specific week configuration without external data
  • Semantic HTML with elements improves accessibility and SEO
  • Zero-dependency calendars work well for date pickers and simple event views
  • Complex scheduling features still benefit from dedicated libraries
  • The Temporal API proposal may eventually simplify date handling further

Frequently asked questions

Does the Intl API work in all modern browsers?

Yes. All major browsers support Intl.Locale, Intl.DateTimeFormat, and Intl.NumberFormat. Check caniuse.com for specific features if you need to support older browsers like Internet Explorer.

How does this handle timezone conversions?

Intl.DateTimeFormat accepts a timeZone option for display purposes. The underlying Date object uses the host system's timezone, so converting between zones requires additional logic.

Can I customize which days are disabled or highlighted?

The tutorial shows the rendering framework. You'd add custom logic to mark unavailable dates or highlight specific days based on your application data.

What's the bundle size compared to a library like date-fns?

Zero bytes for the date logic itself—everything uses browser APIs. Libraries typically add 15-70KB minified and gzipped.

How do I handle recurring events with this approach?

Recurrence logic is beyond what a calendar component handles. You'd need a separate recurrence engine or library for RRULE handling and exception management.

Related