Status: v0.1.x β usable and tested. Per semver, breaking API changes remain possible on minor bumps until 1.0. If you spot a rough edge, please file an issue.
A framework-agnostic, multi-calendar datepicker Web Component powered by Intl.DateTimeFormat.
- 14 calendar systems β Gregorian, Persian, Islamic (3 variants), Hebrew, Buddhist, Japanese, Indian, Ethiopic, Coptic, ROC, and more
- Locale-aware formatting β month/day names, number formatting, and RTL support driven by
Intl - Built-in label translations for English (default), with opt-in entry points for Persian, Arabic, and Hebrew. Other locales fall back to English; supply your own via the
labelsAPI - Multiple picker types β date, range, week, multiple, month, year
- Zero-framework lock-in β works with vanilla HTML, React, Vue, Svelte, or any framework
- SSR-safe β importable in Node/Next.js without crashing; rendering is still client-only
- Form-associated β participates in
<form>submission, validation, and reset - Accessible β full keyboard navigation, ARIA roles, and
prefers-reduced-motionsupport
npm install intl-datepicker<script type="module">
import 'intl-datepicker';
</script>
<intl-datepicker></intl-datepicker>Non-Gregorian calendars must be explicitly imported (they are tree-shakeable):
import 'intl-datepicker';
import 'intl-datepicker/calendars/persian';
import 'intl-datepicker/labels/fa'; // Persian UI strings (optional)<intl-datepicker calendar="persian" locale="fa-IR"></intl-datepicker>Or import all 14 calendars at once:
import 'intl-datepicker/full'; // includes all calendar systems and label setsEnglish labels ship with the main bundle. Persian, Arabic, and Hebrew label sets are tree-shakeable β import the ones you need:
import 'intl-datepicker';
import 'intl-datepicker/labels/fa';<intl-datepicker locale="fa-IR"></intl-datepicker>For any locale without a built-in set (or to override individual strings),
pass a labels object via the attribute or property API.
<intl-datepicker value="2026-03-15"></intl-datepicker><intl-datepicker type="range" min="2026-01-01" max="2026-12-31"></intl-datepicker>Value format: YYYY-MM-DD/YYYY-MM-DD
<intl-datepicker type="week"></intl-datepicker>Value format: YYYY-Www (e.g. 2026-W14)
<intl-datepicker type="multiple" max-dates="5" sort-dates></intl-datepicker>Value format: comma-separated ISO dates
<intl-datepicker type="month"></intl-datepicker>Value format: YYYY-MM
<intl-datepicker type="year"></intl-datepicker>Value format: YYYY
| Attribute | Type | Description |
|---|---|---|
calendar |
string |
Calendar system (see table below). Default: "gregory" |
locale |
string |
BCP 47 locale tag. Default: browser locale |
value |
string |
Initial value in ISO format |
type |
string |
Picker type: date, range, week, multiple, month, year |
min |
string |
Minimum selectable date (ISO) |
max |
string |
Maximum selectable date (ISO) |
for |
string |
ID of an external <input> to bind to |
placeholder |
string |
Input placeholder text |
name |
string |
Form field name |
inline |
boolean |
Always-visible calendar (no popup) |
disabled |
boolean |
Disable the picker |
readonly |
boolean |
Read-only input |
required |
boolean |
Mark as required for form validation |
show-alternate |
boolean |
Show Gregorian equivalent below the calendar |
disabled-dates |
string |
JSON array of ISO dates to disable, e.g. '["2026-01-01","2026-12-25"]' |
disable-weekends |
boolean |
Disable Saturday and Sunday |
date-separator |
string |
Separator for multiple date display. Default: ", " |
max-dates |
number |
Max dates selectable in multiple mode |
sort-dates |
boolean |
Auto-sort selected dates in multiple mode |
months |
number |
Number of side-by-side month panels (1β3) |
presets |
string |
JSON array of range presets (see below) |
no-animation |
boolean |
Disable open/close animations |
show-week-numbers |
boolean |
Show ISO week numbers |
hide-outside-days |
boolean |
Hide days from adjacent months |
allow-input |
boolean |
Allow typing dates directly into the input |
calendar value |
System |
|---|---|
gregory |
Gregorian (default) |
persian |
Persian (Jalali) |
islamic |
Islamic (Umm al-Qura) |
islamic-umalqura |
Islamic (Umm al-Qura) |
islamic-civil |
Islamic (Civil/Tabular) |
islamic-tbla |
Islamic (Tabular) |
hebrew |
Hebrew |
buddhist |
Buddhist |
japanese |
Japanese |
indian |
Indian National |
ethiopic |
Ethiopic |
ethioaa |
Ethiopic (Amete Alem) |
coptic |
Coptic |
roc |
ROC (Minguo/Taiwan) |
| Event | detail |
Description |
|---|---|---|
intl-select |
SelectDetail |
Fired when a date is clicked |
intl-change |
SelectDetail |
Fired on any value change (select, clear, programmatic) |
intl-navigate |
{ year, month, direction } |
Fired when the user navigates months |
intl-open |
β | Cancelable. Fired before popup opens |
intl-close |
β | Cancelable. Fired before popup closes |
The detail shape depends on the picker type:
// type="date" | "month" | "year"
{ type, value, calendar: { year, month, day }, formatted }
// type="range" | "week"
{ type, value, start: { year, month, day }, end: { year, month, day }, formatted }
// type="multiple"
{ type, value, dates: [{ year, month, day }, ...], formatted }const picker = document.querySelector('intl-datepicker');
// Properties
picker.value; // ISO string
picker.valueAsDate; // native Date or null
picker.displayValue; // formatted display string
picker.calendarValue; // CalendarDate object
picker.rangeStart; // ISO string or null (range/week)
picker.rangeEnd; // ISO string or null (range/week)
picker.selectedDates; // CalendarDate[] (multiple)
// Methods
picker.getValue(); // full SelectDetail or null
picker.setValue('2026-04-05'); // set value programmatically
picker.clear(); // clear selection
picker.open(); // open popup
picker.close(); // close popup
picker.goToMonth(2026, 6); // navigate to a specific month
// Callbacks (set via JS only)
picker.mapDays = ({ date, isToday, isDisabled }) => {
if (date.dayOfWeek === 5) return { className: 'friday', content: 'π' };
};
picker.disabledDatesFilter = ({ year, month, day }) => {
return day === 13; // disable all 13ths
};
// Alias for disabledDatesFilter
picker.isDateDisabled = (date) => date.day === 13;<intl-datepicker
type="range"
presets='[
{"label": "Last 7 days", "value": "-6d/today"},
{"label": "This month", "value": "monthStart/monthEnd"},
{"label": "Last 30 days", "value": "-29d/today"}
]'
></intl-datepicker>Preset value uses relative date expressions separated by /:
todayβ today's date-Ndβ N days ago+Ndβ N days from nowmonthStart/monthEndβ start/end of current month
Presets can also be set via JavaScript:
picker.presets = [
{ label: 'This week', value: '-6d/today' },
{ label: 'This month', value: 'monthStart/monthEnd' },
];picker.mapDays = (info) => {
// info: { date, isToday, isSelected, isDisabled, isInRange,
// isRangeStart, isRangeEnd, isCurrentMonth }
// date: { year, month, day, dayOfWeek }
return {
className: 'my-class', // extra CSS class
style: 'color: red', // inline style
content: '<span>!</span>', // HTML appended inside cell
disabled: true, // force-disable this day
hidden: true, // hide this cell
title: 'Tooltip text', // title attribute
};
};Style the component from the outside:
intl-datepicker {
--idp-primary: #2563eb;
--idp-bg: #ffffff;
--idp-text: #1f2937;
--idp-border: #d1d5db;
--idp-hover: #f3f4f6;
--idp-selected-bg: var(--idp-primary);
--idp-selected-text: #ffffff;
--idp-today-border: var(--idp-primary);
--idp-disabled: #9ca3af;
--idp-radius: 8px;
--idp-day-size: 40px;
--idp-font-size: 14px;
--idp-font-family: system-ui, -apple-system, sans-serif;
--idp-range-bg: #dbeafe;
--idp-range-text: var(--idp-text);
--idp-muted: #6b7280;
}Use ::part() for deeper styling:
intl-datepicker::part(input) { border-radius: 12px; }
intl-datepicker::part(calendar) { box-shadow: 0 8px 24px rgba(0,0,0,0.15); }
intl-datepicker::part(day) { border-radius: 50%; }
intl-datepicker::part(header) { background: #f0f0f0; }| Part | Element |
|---|---|
input-wrapper |
Input container |
input |
The <input> element |
calendar |
Calendar popup panel |
header |
Month/year header bar |
header-title |
Header title area |
nav-prev |
Previous navigation button |
nav-next |
Next navigation button |
weekday |
Weekday column header |
day |
Day cell button |
month-cell |
Month cell (month picker view) |
year-cell |
Year cell (year picker view) |
footer |
Footer bar with Today/Clear |
today-btn |
"Today" button |
clear-btn |
"Clear" button |
alternate |
Gregorian alternate display |
presets |
Presets sidebar |
Bind the picker to any existing input:
<input type="text" id="my-input" placeholder="Pick a date">
<intl-datepicker for="my-input" calendar="persian" locale="fa-IR"></intl-datepicker><form>
<intl-datepicker name="birthday" required min="1950-01-01" max="2010-12-31"></intl-datepicker>
<button type="submit">Submit</button>
</form>The component participates in native form submission, validation (required, min/max range checks), and form.reset().
| Key | Action |
|---|---|
Arrow keys |
Move focus between days |
Enter |
Select focused date |
Escape |
Close the popup |
import IntlDatepicker from 'intl-datepicker/react';
function App() {
const ref = useRef(null);
return (
<IntlDatepicker
ref={ref}
calendar="persian"
locale="fa-IR"
type="range"
inline
onSelect={(detail) => console.log(detail)}
onChange={(detail) => console.log(detail)}
onNavigate={(detail) => console.log(detail)}
onOpen={(e) => { /* return false to prevent */ }}
onClose={(e) => { /* return false to prevent */ }}
/>
);
}ref.current.element; // underlying HTMLElement
ref.current.value; // ISO string
ref.current.displayValue; // formatted string
ref.current.calendarValue; // CalendarDate
ref.current.selectedDates; // CalendarDate[]
ref.current.getValue(); // SelectDetail
ref.current.setValue('2026-04-05');
ref.current.clear();
ref.current.open();
ref.current.close();
ref.current.goToMonth(2026, 6);Type declarations are included. Imports:
import 'intl-datepicker';
import type {
IntlDatepickerElement,
SelectDetail,
NavigateDetail,
DatepickerType,
MapDaysFn,
RangePreset,
DisabledDatesFilterFn,
} from 'intl-datepicker';
// React
import IntlDatepicker from 'intl-datepicker/react';
import type { IntlDatepickerProps, IntlDatepickerRef } from 'intl-datepicker/react';Any browser supporting Web Components, Intl.DateTimeFormat, and adoptedStyleSheets:
- Chrome/Edge 73+
- Firefox 101+
- Safari 16.4+
MIT