Skip to content

API Reference ​

Constructor ​

ts
new Datepicker(
  element: HTMLInputElement | string,
  options?: DatepickerOptions
): Datepicker

element can be an HTMLInputElement or a CSS selector string.

Instance methods ​

Open / Close ​

MethodReturnsDescription
open()Promise<void>Open the dropdown. Runs onBeforeOpen guard.
close(reason?)Promise<void>Close the dropdown. Default reason: 'api'.
toggle()Promise<void>Toggle open/closed.
isOpen()booleanWhether the dropdown is currently open.

Value ​

MethodReturnsDescription
setValue(value, triggerChange?)Promise<void>Set value (string, Date, or null). Runs validation.
getValue()stringCurrent formatted value.
getDate()Date | nullCurrently selected date (single mode).
getDates()Date[]Selected dates (multiple mode).
getRange()DateRange | nullSelected range (range mode). DateRange = { from: Date, to: Date }
setRange(from, to, triggerChange?)Promise<void>Set range programmatically (range mode).
setDates(dates, triggerChange?)Promise<void>Set multiple dates (multiple mode).
clear(triggerChange?)Promise<void>Clear all selections.
setToday(triggerChange?)Promise<void>Select today's date.
isValid()Promise<boolean>Validate current value.

Programmatic changes and onChange ​

setValue, setRange, setDates, clear and setToday are silent by default: calling them from your own code does not fire onChange (or the vdp:change event). This mirrors how setting a native input.value from script never fires a change event, and matches flatpickr's setDate(date, triggerChange) convention — so inside onChange you never have to guess whether the update came from the user or from your own code, because your own code simply doesn't trigger it unless you ask it to.

Pass true as the last argument to opt in and notify listeners same as a user-driven change would:

ts
// silent — onChange is NOT called
await dp.setValue('2026-09-01')

// explicit opt-in — onChange IS called, just like a user pick
await dp.setValue('2026-09-01', true)

Selections made by the user (clicking a day, the Today button, typing + blur, the Clear button) always notify — this flag only affects calls you make from your own code.

MethodReturnsDescription
goToDate(date)Promise<void>Navigate calendar to the month of date.
goToMonth(month, year)Promise<void>Navigate to given month (0-indexed) and year.
goToNextMonth()Promise<void>Go to next month.
goToPrevMonth()Promise<void>Go to previous month.
goToNextYear()Promise<void>Go to next year (same month).
goToPrevYear()Promise<void>Go to previous year (same month).
switchView(view)Promise<void>Switch calendar view: 'days' | 'months' | 'years'.

Configuration ​

MethodReturnsDescription
setOptions(partial)voidMerge new options at runtime. Re-renders an open calendar. See below.
setLocale(locale)voidChange locale (string or LocaleConfig).
setTheme(theme)voidChange theme ('light' | 'dark' | 'auto').
refresh()Promise<void>Re-render the open calendar. No-op when closed.
focus()voidFocus the input element.

setOptions(partial) in detail ​

Options are merged into the live instance and take effect immediately:

  • Callbacks are live. Every onX option is read at the moment the event fires, so setOptions({ onClose }) registers a callback that wasn't passed to the constructor, and setOptions({ onClose: undefined }) removes one that was.
  • An open calendar re-renders. minDate, maxDate, disabledDates, disabledWeekdays, locale, footer buttons and the rest apply without closing the dropdown. The re-render does not fire onMonthChange — no month changed.
  • minDate/maxDate re-validate the current value. If the value falls outside the new range, vdp:invalid fires with BELOW_MIN/ABOVE_MAX and the input gets the vdp-invalid class. The value itself is kept — narrowing a range never silently wipes what the user entered. Clear it yourself if that's what you want.

A few options are consumed once during construction and cannot be changed this way: value, defaultValue, initialView, openOnFocus, allowManualInput.

ts
// Keep the end picker's range in sync with the start picker.
dpStart.on('vdp:change', ({ date }) => dpEnd.setOptions({ minDate: date }))

setLocale() and setTheme() are thin wrappers around setOptions() and behave identically.

Events ​

MethodReturnsDescription
on(event, handler)() => voidSubscribe to an event. Returns unsubscribe function.
off(event, handler)voidUnsubscribe from an event.

Lifecycle ​

MethodReturnsDescription
destroy()voidRemove event listeners, clean up DOM, close dropdown.

Static methods ​

MethodReturnsDescription
Datepicker.getInstance(el)Datepicker | nullReturn the existing instance attached to el (element or CSS selector), or null if none.
Datepicker.setDefaults(partial)voidSet global defaults applied to all new instances.
Datepicker.registerLocale(name, config)voidRegister a custom locale.
Datepicker.autoInit(selector?)Datepicker[]Init all matching elements. Default: [data-datepicker].
Datepicker.parse(text, format?)Date | nullParse a date string.
Datepicker.format(date, format?)stringFormat a date.

Datepicker.getInstance ​

Retrieves the Datepicker instance previously created on a given input element. Returns null if the element has no associated instance (not yet initialised, or already destroyed).

ts
Datepicker.getInstance(el: HTMLInputElement | string): Datepicker | null

Useful when you need to control a datepicker from code that doesn't hold a reference to the original instance — for example inside a framework component, an event handler, or a third-party plugin.

ts
// by CSS selector
Datepicker.getInstance('#arrival')?.setValue('2026-09-01')

// by element reference
const input = document.querySelector<HTMLInputElement>('#arrival')!
Datepicker.getInstance(input)?.open()

// safe null-check
const dp = Datepicker.getInstance('#arrival')
if (dp) {
  console.log(dp.getValue())
}

The registry uses a WeakMap internally, so destroyed instances and detached elements are garbage-collected without any manual cleanup.

Keyboard & focus ​

When the calendar opens, no button or day cell receives focus — nothing is highlighted until the user actually starts navigating. Focus is parked on the dropdown element itself (.vdp-dropdown, role="dialog", tabindex="-1") so that keyboard events still reach the calendar.

From there:

KeyEffect
ArrowLeft / ArrowRightFirst press moves focus onto the anchor cell (selected day → today → 1st of the displayed month). Every following press moves ±1 day.
ArrowUp / ArrowDownSame as above, ±1 week.
PageUp / PageDown±1 month (with Shift: ±1 year).
Enter / SpaceSelect the focused day, or activate the focused header/footer button. Selects nothing while no day is focused.
TabCycles through the focusable elements inside the dialog (focus trap). The anchor cell carries tabindex="0", so one Tab reaches the grid.
EscapeClose the calendar.

Arrow keys reach the grid from anywhere in the dialog, so navigation still works right after clicking a header arrow with the mouse. Focus also survives re-renders (month navigation, selection with closeOnSelect: false, range hover preview) — it returns to the same day cell, or to the dialog when that day is no longer displayed.

The month and year views still focus their selected cell when opened, because the user gets there by an explicit click on the header.

.vdp-dropdown sets outline: none, so parking focus on the dialog draws no ring. If you override dropdown styles, keep that rule (or the equivalent :focus-visible handling) to avoid a stray outline around the whole calendar.

keepFocus ​

With keepFocus: true the input gets focus back when a calendar that was holding focus closes (selection, Escape, close()), instead of dropping focus to <body>:

ts
new Datepicker('#departure', { keepFocus: true })

Focus is never stolen from somewhere else — if you close the calendar by clicking another field, that field keeps focus. Returning focus to the input does not reopen the calendar under openOnFocus: true; clicking the (already focused) input reopens it.

Format tokens ​

TokenOutputExample
YYYY4-digit year2026
YY2-digit year26
MM2-digit month07
MMonth (no pad)7
DD2-digit day04
DDay (no pad)4
HH2-digit hour (24h)14
HHour (no pad)14
mm2-digit minutes05
mMinutes (no pad)5
ss2-digit seconds09
sSeconds (no pad)9

Released under the MIT License.