Usage
Use [data-datepicker] for a single date, [data-datepicker-range] for start/end ranges, and [data-calendar] when the calendar should stay inline. The trigger owns aria-expanded and aria-controls; the popover owns role="dialog"; the calendar owns roving day focus and selection state.
The optional Datepicker ESM is a self-contained Moo UI module with no third-party runtime; it owns the visible surface, date math, and selection state. Import @wpmoo/ui/datepicker.js, initialize only the root your page owns, and call dispose() before removing that root in a partial-page or SPA update.
1
2
3
4
5
6
7
8
9
10
11
12
13<div class="field w-100">
<div class="moo-datepicker" id="datepicker-basic" data-datepicker data-datepicker-mode="single" data-datepicker-locale="en" data-datepicker-placeholder="Pick a date">
<label class="form-label" for="datepicker-basic-trigger">Date</label>
<button class="btn btn-outline-secondary moo-datepicker__trigger" id="datepicker-basic-trigger" type="button" data-datepicker-trigger aria-haspopup="dialog" aria-expanded="false" aria-controls="datepicker-basic-popover">
<i class="lucide lucide-calendar" data-icon="inline-start" aria-hidden="true"></i>
<span data-datepicker-label>Pick a date</span>
</button>
<input type="hidden" name="date" value="" data-datepicker-input>
<div class="moo-datepicker__popover" id="datepicker-basic-popover" data-datepicker-popover role="dialog" aria-modal="false" aria-label="Date calendar" hidden>
<div class="moo-calendar" id="datepicker-basic-calendar" data-calendar data-calendar-mode="single" data-calendar-locale="en" aria-label="Date calendar" tabindex="-1"></div>
</div>
</div>
</div>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19<div class="moo-datepicker-time-row">
<div class="field flex-grow-1">
<div class="moo-datepicker" id="datepicker-time-picker" data-datepicker data-datepicker-mode="single" data-datepicker-locale="en" data-datepicker-placeholder="Pick a date">
<label class="form-label" for="datepicker-time-picker-trigger">Date</label>
<button class="btn btn-outline-secondary moo-datepicker__trigger" id="datepicker-time-picker-trigger" type="button" data-datepicker-trigger aria-haspopup="dialog" aria-expanded="false" aria-controls="datepicker-time-picker-popover">
<i class="lucide lucide-calendar" data-icon="inline-start" aria-hidden="true"></i>
<span data-datepicker-label>Pick a date</span>
</button>
<input type="hidden" name="event_date" value="" data-datepicker-input>
<div class="moo-datepicker__popover" id="datepicker-time-picker-popover" data-datepicker-popover role="dialog" aria-modal="false" aria-label="Date calendar" hidden>
<div class="moo-calendar" id="datepicker-time-picker-calendar" data-calendar data-calendar-mode="single" data-calendar-locale="en" aria-label="Date calendar" tabindex="-1"></div>
</div>
</div>
</div>
<div class="field moo-datepicker-time">
<label class="form-label" for="datepicker-time-picker-time">Time</label>
<input type="time" class="form-control" id="datepicker-time-picker-time" name="event_time" value="10:30:00" step="1" autocomplete="off">
</div>
</div>
1
2
3
4
5
6
7
8
9
10
11
12
13
14<div class="field w-100">
<div class="moo-datepicker moo-datepicker--range" id="datepicker-range" data-datepicker-range data-datepicker-mode="range" data-datepicker-locale="en" data-datepicker-placeholder="Pick a date range" data-datepicker-start-value="2026-08-20" data-datepicker-end-value="2026-08-27">
<label class="form-label" for="datepicker-range-trigger">Date range</label>
<button class="btn btn-outline-secondary moo-datepicker__trigger" id="datepicker-range-trigger" type="button" data-datepicker-trigger aria-haspopup="dialog" aria-expanded="false" aria-controls="datepicker-range-popover">
<i class="lucide lucide-calendar-range" data-icon="inline-start" aria-hidden="true"></i>
<span data-datepicker-label>Aug 20, 2026 - Aug 27, 2026</span>
</button>
<input type="hidden" name="start_date" value="2026-08-20" data-datepicker-range-start>
<input type="hidden" name="end_date" value="2026-08-27" data-datepicker-range-end>
<div class="moo-datepicker__popover" id="datepicker-range-popover" data-datepicker-popover role="dialog" aria-modal="false" aria-label="Date range calendar" hidden>
<div class="moo-calendar" id="datepicker-range-calendar" data-calendar data-calendar-mode="range" data-calendar-locale="en" data-calendar-start-value="2026-08-20" data-calendar-end-value="2026-08-27" aria-label="Date range calendar" tabindex="-1"></div>
</div>
</div>
</div>
1
2
3
4
5
6
7
8
9
10
11
12
13<div class="field w-100">
<div class="moo-datepicker" id="datepicker-birth-date" data-datepicker data-datepicker-mode="single" data-datepicker-locale="en" data-datepicker-placeholder="Select date" data-datepicker-min-date="1926-01-01" data-datepicker-max-date="2026-12-31">
<label class="form-label" for="datepicker-birth-date-trigger">Date of birth</label>
<button class="btn btn-outline-secondary moo-datepicker__trigger" id="datepicker-birth-date-trigger" type="button" data-datepicker-trigger aria-haspopup="dialog" aria-expanded="false" aria-controls="datepicker-birth-date-popover">
<i class="lucide lucide-calendar" data-icon="inline-start" aria-hidden="true"></i>
<span data-datepicker-label>Select date</span>
</button>
<input type="hidden" name="birth_date" value="" data-datepicker-input>
<div class="moo-datepicker__popover" id="datepicker-birth-date-popover" data-datepicker-popover role="dialog" aria-modal="false" aria-label="Date of birth calendar" hidden>
<div class="moo-calendar" id="datepicker-birth-date-calendar" data-calendar data-calendar-mode="single" data-calendar-locale="en" data-calendar-min-date="1926-01-01" data-calendar-max-date="2026-12-31" data-calendar-caption-layout="dropdown" aria-label="Date of birth calendar" tabindex="-1"></div>
</div>
</div>
</div>
1
2
3
4
5
6
7
8
9
10
11
12
13<div class="field w-100">
<div class="moo-datepicker" id="datepicker-presets" data-datepicker data-datepicker-mode="single" data-datepicker-locale="en" data-datepicker-placeholder="Pick a date" data-datepicker-show-presets="true">
<label class="form-label" for="datepicker-presets-trigger">Schedule date</label>
<button class="btn btn-outline-secondary moo-datepicker__trigger" id="datepicker-presets-trigger" type="button" data-datepicker-trigger aria-haspopup="dialog" aria-expanded="false" aria-controls="datepicker-presets-popover">
<i class="lucide lucide-calendar" data-icon="inline-start" aria-hidden="true"></i>
<span data-datepicker-label>Pick a date</span>
</button>
<input type="hidden" name="schedule_date" value="" data-datepicker-input>
<div class="moo-datepicker__popover" id="datepicker-presets-popover" data-datepicker-popover role="dialog" aria-modal="false" aria-label="Schedule date calendar" hidden>
<div class="moo-calendar" id="datepicker-presets-calendar" data-calendar data-calendar-mode="single" data-calendar-locale="en" data-calendar-show-presets="true" aria-label="Schedule date calendar" tabindex="-1"></div>
</div>
</div>
</div>
1<div class="moo-calendar" id="datepicker-inline-calendar" data-calendar data-calendar-mode="single" data-calendar-locale="en" data-calendar-value="2026-08-18" data-calendar-min-date="2026-08-10" data-calendar-max-date="2026-09-15" data-calendar-disabled-dates='["2026-08-22", "2026-08-23"]' data-calendar-show-presets="true" aria-label="Release calendar" tabindex="-1"></div>
1
2
3
4
5
6
7
8
9
10
11
12<div class="field w-100">
<div class="moo-datepicker" id="datepicker-rtl-arabic" data-datepicker data-datepicker-mode="single" data-datepicker-locale="ar" data-datepicker-placeholder="اختر تاريخا" data-datepicker-value="2026-08-18" dir="rtl">
<button class="btn btn-outline-secondary moo-datepicker__trigger" id="datepicker-rtl-arabic-trigger" type="button" data-datepicker-trigger aria-haspopup="dialog" aria-expanded="false" aria-controls="datepicker-rtl-arabic-popover" aria-label="اختر تاريخا">
<i class="lucide lucide-calendar" data-icon="inline-start" aria-hidden="true"></i>
<span data-datepicker-label>Aug 18, 2026</span>
</button>
<input type="hidden" name="rtl_arabic_date" value="2026-08-18" data-datepicker-input>
<div class="moo-datepicker__popover" id="datepicker-rtl-arabic-popover" data-datepicker-popover role="dialog" aria-modal="false" aria-label="اختر تاريخا calendar" hidden>
<div class="moo-calendar" id="datepicker-rtl-arabic-calendar" data-calendar data-calendar-mode="single" data-calendar-locale="ar" data-calendar-value="2026-08-18" aria-label="اختر تاريخا calendar" tabindex="-1"></div>
</div>
</div>
</div>
1
2
3
4
5
6
7
8
9
10
11
12<div class="field w-100">
<div class="moo-datepicker" id="datepicker-rtl-hebrew" data-datepicker data-datepicker-mode="single" data-datepicker-locale="he" data-datepicker-placeholder="בחר תאריך" data-datepicker-value="2026-08-18" dir="rtl">
<button class="btn btn-outline-secondary moo-datepicker__trigger" id="datepicker-rtl-hebrew-trigger" type="button" data-datepicker-trigger aria-haspopup="dialog" aria-expanded="false" aria-controls="datepicker-rtl-hebrew-popover" aria-label="בחר תאריך">
<i class="lucide lucide-calendar" data-icon="inline-start" aria-hidden="true"></i>
<span data-datepicker-label>Aug 18, 2026</span>
</button>
<input type="hidden" name="rtl_hebrew_date" value="2026-08-18" data-datepicker-input>
<div class="moo-datepicker__popover" id="datepicker-rtl-hebrew-popover" data-datepicker-popover role="dialog" aria-modal="false" aria-label="בחר תאריך calendar" hidden>
<div class="moo-calendar" id="datepicker-rtl-hebrew-calendar" data-calendar data-calendar-mode="single" data-calendar-locale="he" data-calendar-value="2026-08-18" aria-label="בחר תאריך calendar" tabindex="-1"></div>
</div>
</div>
</div>
1
2
3
4
5
6
7
8
9
10
11
12<div class="field w-100">
<div class="moo-datepicker" id="datepicker-rtl-english" data-datepicker data-datepicker-mode="single" data-datepicker-locale="en" data-datepicker-placeholder="Pick a date" data-datepicker-value="2026-08-18">
<button class="btn btn-outline-secondary moo-datepicker__trigger" id="datepicker-rtl-english-trigger" type="button" data-datepicker-trigger aria-haspopup="dialog" aria-expanded="false" aria-controls="datepicker-rtl-english-popover" aria-label="Pick a date">
<i class="lucide lucide-calendar" data-icon="inline-start" aria-hidden="true"></i>
<span data-datepicker-label>Aug 18, 2026</span>
</button>
<input type="hidden" name="rtl_english_date" value="2026-08-18" data-datepicker-input>
<div class="moo-datepicker__popover" id="datepicker-rtl-english-popover" data-datepicker-popover role="dialog" aria-modal="false" aria-label="Pick a date calendar" hidden>
<div class="moo-calendar" id="datepicker-rtl-english-calendar" data-calendar data-calendar-mode="single" data-calendar-locale="en" data-calendar-value="2026-08-18" aria-label="Pick a date calendar" tabindex="-1"></div>
</div>
</div>
</div>
JavaScript
The module is explicit and idempotent. It does not scan the document when imported, and it owns its state internally with no global leak.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22import Datepicker, {
MooCalendar,
MooDateRangePicker,
} from "@wpmoo/ui/datepicker.js";
const datepicker = Datepicker.getOrCreateInstance(
document.querySelector("[data-datepicker]")
);
const range = MooDateRangePicker.getOrCreateInstance(
document.querySelector("[data-datepicker-range]")
);
const calendar = MooCalendar.getOrCreateInstance(
document.querySelector("#datepicker-inline-calendar")
);
datepicker.setDate("2026-08-18");
range.setDates("2026-08-20", "2026-08-27");
calendar.clear();
datepicker.dispose();
range.dispose();
calendar.dispose();
Values
Hidden form values use yyyy-mm-dd strings. Public getters return a local Date object by default, or the same yyyy-mm-dd serialization when called with that format string. The locale option controls visible formatting in the calendar and trigger label, defaulting to en.
Component reference
- Status
- Accepted
- Runtime
- optional Moo UI ESM
- Markup
- Moo UI documented extension
- Reference
-
The trigger and presets are Bootstrap button primitives; the popover and calendar are Moo UI markup with natively owned date, selection, and keyboard behavior. Bootstrap ships no date picker component. For the button surface, see the
Bootstrap Buttons documentation.
For the popover surface and form fields, see the
Bootstrap Popovers documentation.