timepicker-ui
4.6.0GitHub
timepicker-ui
Guides / Layouts and Fields

Date Picker Layouts and Fields

Several months, range presets, two fields for a range and per-day content. All of these are opt-in in @pglejzer/datepicker-ui, the companion of timepicker-ui.

Several months

#

calendar.months shows months side by side in a docked or inline picker. The picker shows as many months as fit the width, up to the number you set. The width is read when the picker opens and on setOptions(), not on resize. A modal picker always shows one month, and a range picker in a modal or full screen keeps its vertical month list.

Two months
import DatepickerUI from "@pglejzer/datepicker-ui";
import "@pglejzer/datepicker-ui/index.css";
new DatepickerUI("#date", {
calendar: { months: 2, pageBy: "view" },
}).create();

calendar.pageBy sets how far the arrows and swipes move: "view" (the default) pages by the number of months shown, "month" by one. Outside days are not shown with several months. The navigate event reports the first month shown.

Range presets

#

calendar.range.presets adds shortcuts for ranges. A value is a { start, end } object of dates or ISO strings, or a function returning one. The function runs each time the picker refreshes, so relative presets stay current.

Presets
import DatepickerUI from "@pglejzer/datepicker-ui";
import "@pglejzer/datepicker-ui/index.css";
const day = 24 * 60 * 60 * 1000;
new DatepickerUI("#range", {
calendar: {
mode: "range",
range: {
presets: [
{
label: "Last 7 days",
value: () => ({
start: new Date(Date.now() - 6 * day),
end: new Date(),
}),
},
{
label: "Q4 2026",
value: { start: "2026-10-01", end: "2026-12-31" },
},
],
},
},
}).create();

Presets show as a column beside the calendar in a docked or inline picker when there is room, and as a row of chips under the header otherwise. A modal and a full-screen picker always use the chip row. They are hidden in the input view. labels.presets names the group.

A click completes the range as if the user had picked both ends. It emits range:end, not select:day, and follows behavior.closeOnSelect and behavior.commitOnChange. A preset is disabled when its value is invalid or the limits refuse the range.

Two fields for a range

#

ui.endInput takes the selector or element of a second <input> on your page. The page owns both inputs, their labels, styling and names. It is read on create() and only used in range mode. A value that is not an <input> emits invalid-option and the picker keeps one field.

index.html
<input id="start" name="checkin" />
<input id="end" name="checkout" />
Two fields
import DatepickerUI from "@pglejzer/datepicker-ui";
import "@pglejzer/datepicker-ui/index.css";
new DatepickerUI("#start", {
calendar: { mode: "range" },
ui: { endInput: "#end" },
}).create();

Each field shows its own date. Clicking or pressing Alt+Down Arrow in the End field opens the picker on the end, and the class dp-ui__input--active marks the field that the next pick fills. Picking the start makes End active. The picker does not style your inputs, so add your own rule for that class:

index.css
.dp-ui__input--active {
outline: 2px solid #6750a4;
}

Typing commits only a full pair, or two empty fields. A half-filled pair commits nothing and shows no error. A reversed pair emits invalid-date with labels.invalidRange on the End field. ui.mask works in both fields.

Fixed weeks and outside days

#

ui.fixedWeeks always renders six rows, so the picker keeps one height from month to month. ui.outsideDays: true shows neighbouring days and keeps them selectable. { selectable: false } shows them dimmed and inert: they get dp-ui__date--disabled and aria-disabled, and clicks are ignored.

Fixed weeks
import DatepickerUI from "@pglejzer/datepicker-ui";
import "@pglejzer/datepicker-ui/index.css";
new DatepickerUI("#date", {
ui: { fixedWeeks: true, outsideDays: { selectable: false } },
}).create();

Per-day content

#

calendar.renderDate returns content shown under the day number, such as a price. Return a string, a Node, or null for nothing. A string is always text and never HTML. The text is also appended to the accessible name of the day. An error thrown inside is rethrown asynchronously and the day gets no content.

Prices
import DatepickerUI from "@pglejzer/datepicker-ui";
import "@pglejzer/datepicker-ui/index.css";
new DatepickerUI("#date", {
calendar: {
renderDate: (date) =>
date.getDate() % 3 === 0 ? `$${80 + date.getDate() * 2}` : null,
},
}).create();

Cells with content get the class dp-ui__date--has-content, and the rows grow to fit it.

Decorating days

#

calendar.decorateDate returns a DateDecoration for a day, or undefined. An error thrown inside is rethrown asynchronously and the day counts as undecorated.

DateDecoration
FieldEffect
classNameAdded to the day cell.
badgeShows a small dot.
labelAppended to the accessible name of the day.
index.ts
import DatepickerUI from "@pglejzer/datepicker-ui";
import "@pglejzer/datepicker-ui/index.css";
new DatepickerUI("#date", {
calendar: {
decorateDate: (date) =>
date.getDay() === 5
? { className: "payday", badge: true, label: "Payday" }
: undefined,
},
}).create();
#
Esc