The inline and docked calendars support keyboard navigation. Enter on the date input focuses a day. Tab enters or leaves the grid, and the arrow keys move by day or week. Home and End follow firstDay, and Left and Right follow isRTL. PageUp and PageDown change the month. Hold Shift with them to change the year. Enter or Space selects an enabled day.
Disabled dates stay focusable, and screen readers announce them as unavailable. Navigation respects configured bounds and keeps focus after redraws. A modal calendar moves focus to the selected day when it opens and back to the input when it closes.
An input's initial value is the selection, and a form reset reads the selection back from the reset values. If a typed date is outside the bounds, falls on a disabled weekend or is refused by disableDayFn, the input goes back to the selected date.
Add date-picker to a text input. AutoInit() starts every
.date-picker except those marked no-autoinit.
Pass openByDefault: true to show the calendar under the
field. The calendar is inline, not a modal. With the default options
it is hidden (openByDefault: false), and clicking the
input does not reveal it.
<div class="field">
<input type="text" class="date-picker" id="birthdate">
<label for="birthdate">Birthdate</label>
</div>
Expressive.Datepicker.init(document.querySelectorAll('.date-picker'), {
openByDefault: true
});
Wrap the input in its own .field (or another small parent). The
picker inserts the calendar after that parent, not after the input itself.
Initialization
The IIFE bundle exposes Expressive.Datepicker.
Call init yourself when you need options other than the defaults,
or let Expressive.AutoInit() start every .date-picker.
AutoInit uses the defaults, so the calendar stays hidden until you pass
openByDefault (or a working displayPlugin pair, below).
document.addEventListener('DOMContentLoaded', function() {
const elems = document.querySelectorAll('.date-picker');
const instances = Expressive.Datepicker.init(elems, {
openByDefault: true
});
});
You can also pass per-instance options through AutoInit:
Expressive.AutoInit(document.body, {
Datepicker: { openByDefault: true }
});
Display
The calendar is a .datepicker-container in the page, unless
displayPlugin: 'modal' puts it in a dialog. There is no
autoClose option.
Always visible
openByDefault: true leaves the calendar in the layout. That is the
reliable way to show it.
Docked popover
displayPlugin: 'docked' wraps the calendar in a
.display-docked popover. The popover appears when the input is
clicked or focused with Enter, and hides when you click outside. The plugin only animates
the wrapper. If openByDefault is still false, the calendar
inside stays display: none. Use both:
Expressive.Datepicker.init(elem, {
displayPlugin: 'docked',
openByDefault: true
});
The popover stays inside the input's parent and accounts for its
positioning and scroll offsets. You can use positioned wrappers such as
.field directly.
Optional displayPluginOptions: margin (default
5), transition (10),
duration (250), and align
('left').
Modal dialog
displayPlugin: 'modal' opens the calendar in a modal
<dialog class="display-modal"> when the input is
clicked or focused with Enter. It looks like a basic dialog over a
scrim. The dialog takes the input's label as its name, and focus
moves to the selected day.
Picking a day writes nothing until Ok. Cancel, Escape or a press on
the scrim closes the dialog without changing the input and calls
onCancel. Focus then goes back to the input. A modal
calendar always shows Cancel and Ok, so it sets
openByDefault: true and autoSubmit: false
for you.
Expressive.Datepicker.init(elem, {
displayPlugin: 'modal'
});
A selective Sass build needs "dialog" and
"modal-display" in $components.
container renders the calendar into a specific element and does not
hide it. Pass a selector or a DOM node.
Options
| Name | Type | Default | Description |
|---|---|---|---|
format |
String or Function | 'mmm dd, yyyy' |
Output written to the input, or a function that takes a Date and returns a string. |
parse |
Function | null |
Turn the current input string back into a Date. Receives (value, format), where format is the configured string or an empty string for a function formatter. Blank inputs skip parsing. |
isDateRange |
Boolean | false |
Select a start date and an end date. |
dateRangeEndEl |
String | null |
Selector for an existing end-date input. If you omit it, the picker creates a second input. |
isMultipleSelection |
Boolean | false |
Toggle several dates. The picker creates extra inputs as dates are added. |
defaultDate |
Date | null |
Initial date to view. Falls back to the input's current value, then today. |
defaultEndDate |
Date | null |
Initial end date when isDateRange is true. |
setDefaultDate |
Boolean | false |
If true, defaultDate is also the selected value. |
setDefaultEndDate |
Boolean | false |
If true, defaultEndDate is also the selected end value. |
disableWeekends |
Boolean | false |
Prevent selecting Saturday and Sunday. |
disableDayFn |
Function | null |
Return true to disable that day. Receives a Date. |
firstDay |
Number | 0, or the language's |
First day of the week. 0 is Sunday, 1 is Monday. With a lang on or around the input, it defaults to that language's first day where the browser knows it. |
minDate |
Date | null |
Earliest selectable date. |
maxDate |
Date | null |
Latest selectable date. |
yearRange |
Number or Array | 10 |
Years on either side of the viewed year, or [minYear, maxYear]. |
yearRangeReverse |
Boolean | false |
Sort the year list in reverse order. |
isRTL |
Boolean | false |
Render the calendar right-to-left. |
showMonthAfterYear |
Boolean | false |
Show the month after the year in the title. |
showDaysInNextAndPreviousMonths |
Boolean | false |
Render days that fall in the adjoining months. |
openByDefault |
Boolean | false |
If true, the calendar is visible. If false, it gets display: none. |
container |
Element or String | null |
Element or selector to render the calendar into. When set, the calendar is not hidden. |
showClearBtn |
Boolean | false |
Show a Clear button in the footer. |
autoSubmit |
Boolean | true |
If true, selecting a day writes the input immediately. If false, Ok and Cancel buttons are added. |
i18n |
Object | See below | Labels and month and weekday names. Partial objects are merged with the defaults. |
events |
Array | [] |
Strings from Date.toDateString(). Matching days get a has-event class. The default stylesheet does not style that class. |
onSelect |
Function | null |
Called when a date is selected. Receives the Date. |
onDraw |
Function | null |
Called after the calendar HTML is redrawn. |
onInputInteraction |
Function | null |
Called when the input is clicked or confirmed with Enter. |
onConfirm |
Function | null |
Called once when Ok is activated by click, Enter or Space. The picker adds this button when autoSubmit is false. |
onCancel |
Function | null |
Called once when Cancel is activated by click, Enter or Space. The picker adds this button when autoSubmit is false. |
displayPlugin |
String | null |
Set to 'docked' for a click-to-open popover, paired with openByDefault: true, or 'modal' for a dialog with Cancel and Ok. |
displayPluginOptions |
Object | null |
Options for the docked plugin: margin, transition, duration, align. The modal plugin takes none. |
Date format options
Without a custom parse callback, the picker reads YYYY-MM-DD input as a local calendar date. Impossible dates such as 2023-02-29 do not change the selection. The picker reads text in its own string format with its i18n names. So 05/10/2026 in dd/mm/yyyy is 5 October, and a German Mär 15, 2026 reads back. Other strings go to the browser's date parser. Supply parse for other input formats.
The custom parser also runs when the picker initializes, and when it reopens by click or Enter. Clearing a single-date input clears its selection, so confirming does not restore the deleted date.
Use these tokens in the format string.
| Key | Description | Output |
|---|---|---|
d |
Date of the month. | 1 to 31 |
dd |
Date of the month, two digits. | 01 to 31 |
ddd |
Weekday short name from i18n. |
Sun to Sat |
dddd |
Weekday full name from i18n. |
Sunday to Saturday |
m |
Month of the year. | 1 to 12 |
mm |
Month of the year, two digits. | 01 to 12 |
mmm |
Month short name from i18n. |
Jan to Dec |
mmmm |
Month full name from i18n. |
January to December |
yy |
Two-digit year. | 26 |
yyyy |
Four-digit year. | 2026 |
Internationalization
Put a lang attribute on the input or around it, such as
<html lang="de">, to take the month and weekday names from
that language, as Intl writes them. The first day of the week
follows that language too, where the browser knows it. Without a
lang, the English defaults stay. The button words are not dates,
so pass them yourself.
Pass a partial i18n object. Keys you pass win over the language. Missing keys keep the language's names, or the English defaults.
| Key | Default |
|---|---|
cancel |
'Cancel' |
clear |
'Clear' |
done |
'Ok' |
previousMonth |
'Previous month' |
nextMonth |
'Next month' |
months |
['January', …, 'December'] |
monthsShort |
['Jan', …, 'Dec'] |
weekdays |
['Sunday', …, 'Saturday'] |
weekdaysShort |
['Sun', …, 'Sat'] |
weekdaysAbbrev |
['S', 'M', 'T', 'W', 'T', 'F', 'S'] |
Date range
Set isDateRange: true. Click a start day, then an end day that is on or
after it. Point dateRangeEndEl at a second input, or omit it and the
picker creates a second input next to the first.
An authored end input supports typing and Enter-to-calendar navigation. A range with only an end date can be reopened safely. Clearing either input keeps the other date and removes the range shading until both dates are set. An edit that would reverse the range restores that field's previous value.
Each input fires its own bubbling change event when the picker writes its value. Destroying the picker keeps authored inputs and removes generated ones. Generated inputs opt out of AutoInit, so repeated initialization does not create another picker for them.
Expressive.Datepicker.init(document.getElementById('datepicker-range'), {
openByDefault: true,
isDateRange: true,
dateRangeEndEl: '#datepicker-range-end'
});
Multiple dates
Set isMultipleSelection: true. Click a day to add it, and click it again to
remove it. Each selected date gets its own input.
Removing the last date or using Clear keeps the original input empty.
The picker removes extra generated inputs when their dates are deselected or the picker is destroyed.
Expressive.Datepicker.init(document.getElementById('datepicker-multi'), {
openByDefault: true,
isMultipleSelection: true
});
Methods
All methods are called on the plugin instance. You can get the instance like this:
const instance = Expressive.Datepicker.getInstance(elem);
The low-level renderRow, renderBody, and renderTable
helpers accept only calendar rows, cells, and day buttons. Use renderDay
to create cells. Unsupported elements or attributes, including event handlers and styles,
throw TypeError. Day buttons require type="button".
Arbitrary custom HTML is no longer accepted.
.toString();
String form of the selected date, using format. You can pass another date and format.
instance.toString();
instance.toString(someDate, 'yyyy-mm-dd');
.setDate();
Select a date and move the calendar to it.
Date (optional): Date to select.
instance.setDate(new Date());
.gotoDate();
Change the visible month without changing the selection.
Date: Date whose month should be shown.
instance.gotoDate(new Date());
.destroy();
Destroy the plugin instance, remove the calendar, and tear down its event handlers.
instance.destroy();
Properties
| Name | Type | Description |
|---|---|---|
el |
Element | The input the plugin was initialized with. |
options |
Object | The options the instance was initialized with. |
date |
Date | The selected date, or the range start. |
endDate |
Date | The range end, when isDateRange is true. |
dates |
Array | The selected dates when isMultipleSelection is true. |