Date and time pickers
DockedDateTimePicker is the docked date picker with the time next to the calendar: a column of
hours, one of minutes and, on a 12 hour locale, one for AM and PM. It is the shape a browser gives
an <input type="datetime-local">, and it is deliberately not the Material clock
dial, which is built for a thumb rather than a pointer. Reach for it when the day and the time
belong to one decision, like the start of a meeting or a shift.
value is a local YYYY-MM-DDTHH:mm string, the day-precision YYYY-MM-DD of the other pickers extended by a time. It is built from local calendar fields,
so it never slips a day across a timezone boundary.
Usage
value is bindable. Typing in the field and picking a day and a time stay in sync;
inside the panel the selection is provisional until OK confirms it, and Cancel discards it. Everything the date side does is unchanged, including typing in
the field, and the time is confirmed by the same OK.
The field holds the value in the format of the locale, and it only becomes a value once the text
describes a whole moment, so a date with no time yet leaves it empty. On a 12 hour clock the day
period has to be typed too: 08/17/2025, 07:30 is two different moments, and picking one
silently is worse than waiting for the rest.
Value: 2025-08-17T14:30
<script lang="ts">
import { DockedDateTimePicker } from 'noph-ui'
let meeting = $state<string | undefined>('2025-08-17T14:30')
</script>
<DockedDateTimePicker bind:value={meeting} label="Starts at" />Below 600dp the columns move under the calendar instead of beside it, so the panel still fits a phone. The switch is a media query, like the range picker's. Narrow the window to see it change.
Minutes and the clock
minuteStep sets how finely the minute column is cut, five minutes by default. It only
governs the column: a minute typed into the field is taken exactly as it is, and a value the app
sets keeps whatever minute it holds. defaultTime is the time a first pick starts from
when the field is still empty, and hour12 overrides the clock the locale would choose.
Value: undefined
<DockedDateTimePicker
bind:value={shift}
label="Shift start"
minuteStep={15}
defaultTime="09:00"
hour12={false}
/>Localisation
Left to itself the clock follows the locale, and so does everything around it: the order of the
fields, the digits, the names of the day periods and whether there is a third column at all. locale takes a BCP 47 tag, and firstDayOfWeek overrides the week start the
locale implies, exactly as on the date picker.
<DockedDateTimePicker bind:value={germanMoment} locale="de-DE" label="Beginnt am" />Bounding a moment
min and max take a whole moment here, not only a day. Days outside the
range are unselectable, and on the first and the last day the hours and minutes outside it are
greyed out too. Picking such a day pulls the time to the nearest minute that day still allows, so
the selection is never a moment the bounds refuse. A bare YYYY-MM-DD still works: as min it means the start of that day, as max the end of it.
<DockedDateTimePicker
label="Appointment"
min="2025-08-18T09:30"
max="2025-08-20T16:00"
/>isDateEnabled is called with the full moment rather than a bare day, so a rule can turn
on the time as well as the date.
Forms and validation
Passing a name submits the ISO moment with the surrounding form through a hidden
input, and the constraints stay on the visible field, so a blocked submit reports against a
control the browser can focus. The timing is the same as on the date picker: :user-invalid switches on at a submit attempt and on blur, never while a moment is
still being typed. issues replaces the supporting text with your own messages.
Nothing submitted yet.
<form onsubmit={handleSubmit} novalidate>
<DockedDateTimePicker
bind:value={formValue}
label="Starts at"
name="startsAt"
issues={formIssues}
required
/>
<Button type="submit" variant="filled">Submit</Button>
</form>Shared with the date picker
The calendar half is the docked date picker, so its documentation applies here unchanged. See reacting to a change, text field variants, disabling individual days, restricting the year menu, days of neighbouring months and controlling the calendar. For a day on its own, a modal, or a start and an end day, use DockedDatePicker, DatePickerDialog or DateRangePicker.
Theming
Colours and shapes come from the theme, and every part exposes a custom property for the cases the
theme cannot reach. Set them on the picker itself; they inherit into the calendar and the columns.
The calendar half also takes every --np-date-picker-* token.
| Property | Default |
|---|---|
--np-docked-date-time-picker-container-color | --np-color-surface-container-high |
--np-docked-date-time-picker-container-shape | --np-shape-corner-large |
--np-docked-date-time-picker-container-width | 22.5rem (360dp), the calendar side only |
--np-docked-date-time-picker-column-width | 4.5rem (72dp) |
--np-date-picker-time-selected-container-color | --np-color-secondary-container |
--np-date-picker-time-selected-label-color | --np-color-on-secondary-container |
Example
<DockedDateTimePicker
bind:value
label="Starts at"
--np-date-picker-time-selected-container-color="var(--np-color-tertiary)"
--np-date-picker-time-selected-label-color="var(--np-color-on-tertiary)"
--np-date-picker-date-selected-container-color="var(--np-color-tertiary)"
--np-date-picker-date-selected-label-color="var(--np-color-on-tertiary)"
--np-docked-date-time-picker-column-width="5rem"
/>Motion and gestures
The calendar half moves exactly as the date picker does, on the theme's own
motion tokens, and the month and year lists slide down over the calendar rather than in its place,
so opening one leaves the panel the same size and the columns beside it where they are. The time
columns add nothing of their own beyond a colour cross-fade on the selection: they scroll, and
they open centred on the selected value so the values around it are in view. After that they only
scroll as far as they have to, because pulling the list back to the middle under a pointer that is
still picking makes the next click land somewhere else. Every transition is wrapped in prefers-reduced-motion: no-preference.
Accessibility
Each time column is a role="listbox" of role="option" buttons with a
single roving tab stop, so the three columns are three stops in the tab order rather than a
hundred and twenty. The selected option carries aria-selected, and an option outside min and max carries aria-disabled so it is still readable
rather than skipped. hourLabel, minuteLabel and dayPeriodLabel name the columns.
| Key | Moves |
|---|---|
| ↑ ↓ | One option, wrapping at either end |
| Home End | First or last option of the column |
| Tab | The next column |
| Enter Space | Select the focused option |
| Esc | Close the panel |
The calendar keeps its own grid keyboard model, and every label string is a prop, so a localised app can translate the whole control.
API
DockedDateTimePicker
Takes every DockedDatePicker prop, with value as a YYYY-MM-DDTHH:mm string, min and max as either a moment or a bare day, and isDateEnabled called with the
full moment. label defaults to 'Date and time', openCalendarLabel to 'Show date and time picker' and invalidDateMessage to 'Enter a valid date and time.' On top of those:
| Attribute | Type | Default | Description |
|---|---|---|---|
minuteStep | number | 5 | Spacing of the minute column. Typed and preset minutes are kept as they are. |
hour12 | boolean | from locale | Forces a 12 or 24 hour clock, and with it the AM/PM column. |
defaultTime | string | '00:00' | Time the columns open on while the field is still empty, as HH:mm. |
hourLabel / minuteLabel / dayPeriodLabel | string | 'Hour' / 'Minute' / 'AM or PM' | Accessible names of the three columns. |
Bindables
| Attribute | Type | Description |
|---|---|---|
value | string | null | undefined | Selected moment as YYYY-MM-DDTHH:mm. |
displayMonth | string | Month on screen, as an ISO day. Follows value until navigated, and is restored to
what you set when the panel closes. |
open | boolean | Whether the docked panel is showing. |
element | HTMLSpanElement | The picker's root element, which is the text field itself. |
Time helpers
The minute-precision maths behind the picker is exported too, so an app can share the same timezone-safe handling. The day-precision counterparts live with the date picker.
| Function | Description |
|---|---|
toISODateTime(date) / parseISODateTime(value, dateOnlyMinutes) | Serialises and parses YYYY-MM-DDTHH:mm from local fields. Parsing also takes a bare
day, landing it on the minute given. |
parseDateTimeInput(text, locale, hour12) / formatDateTime | Typed text and formatted output for a whole moment. |
formatTime(date, locale, hour12) / getDateTimePattern(locale) | The time on its own, and the MM/DD/YYYY, hh:mm AM style hint of a locale. |
uses12HourClock(locale) / getHourLabels / getDayPeriodLabels | The clock of a locale, and the labels the time columns are built from. |
minutesOfDay / withMinutes / toISOTime / compareTimes / isTimeWithin | Minute-precision maths on a moment. |