Range Date Time Calendar
Range Date Time Calendar widgets are Input Fields that let the user build one or more date-time ranges from an always-visible, multi-month calendar. The user picks a start day and an end day on the grid, then a start time and an end time from the two embedded time pickers below it; committing the end time adds the range as a removable pill.
The widget emits an array of { start, end } local ISO date-time strings (YYYY-MM-DDTHH:mm:ss), one entry per selected range. For a single date-time value, use the Date Time Calendar; for the compact popover variant with a typed input trigger, use the Range Date Time Picker.
import { gui } from '@golemui/gui-shared';
export default [ gui.inputs.rangeDateTimeCalendar('bookingSlots', { label: 'Booking slots', }),];{ "$schema": "https://golemui.com/schemas/form.schema.json", "form": [ { "kind": "input", "type": "rangeDateTimeCalendar", "path": "bookingSlots", "label": "Booking slots" } ]}Use these props to customize your Range Date Time Calendar widget. It accepts date props (for the day grid) and time props (for the two pickers), all listed below. With numberOfMonths greater than 1, the time pickers render on the first panel only.
| prop | type | description |
|---|---|---|
allowCustomTime | boolean | Allow typing a time in each picker; when false (default) times come only from the grid |
dayCountAriaLabel | string | Localizable | Aria label template for the per-day count badge; {count} token substituted. Defaults to {count} ranges |
dayFormat | string | How day numbers are formatted. Defaults to numeric |
disabledDayCountAriaLabel | string | Localizable | Aria label template for the per-day disabled-spans badge; {count} token substituted. Defaults to {count} disabled ranges |
disabledRangeMessage | string | Localizable | Error shown when a selected range overlaps a disabled span |
disabledRanges | array | Array of { start, end } date-time spans that render disabled (a whole day is 00:00:00–23:59:59) |
endTimeLabel | string | Localizable | Visible heading on the end time picker. Defaults to End time |
hint | string | A description to display below the calendar |
hourFormat | '12' | '24' | Force 12- or 24-hour entry. Defaults to the user’s locale |
maxDateTime | string | Latest allowed instant (ISO date-time, inclusive) |
maxDateTimeMessage | string | Localizable | Error shown when a selection is after maxDateTime |
minDateTime | string | Earliest allowed instant (ISO date-time, inclusive) |
minDateTimeMessage | string | Localizable | Error shown when a selection is before minDateTime |
minuteStep | number | Granularity of the time pickers |
monthFormat | string | How the month heading is formatted. Defaults to long |
nextMonthAriaLabel | string | Accessible label for the next month navigation button |
nextMonthIcon | string | Custom CSS class for the next month button |
noAvailableTimesMessage | string | Localizable | Message shown when the bounds yield no selectable times. Defaults to No available times |
numberOfMonths | number | Number of months to display simultaneously. Defaults to 1 |
prevMonthAriaLabel | string | Accessible label for the previous month navigation button |
prevMonthIcon | string | Custom CSS class for the previous month button |
removePillAriaLabel | string | Aria label for the remove pill action |
startTimeLabel | string | Localizable | Visible heading on the start time picker. Defaults to Start time |
weekdayFormat | string | How weekday headers are formatted. Defaults to narrow |
Use the property hint to add a description below the calendar.
import { gui } from '@golemui/gui-shared';
export default [ gui.inputs.rangeDateTimeCalendar('bookingSlots', { hint: 'Pick a day, then a start and end time to add a slot', minuteStep: 30, label: 'Booking slots', }),];{ "$schema": "https://golemui.com/schemas/form.schema.json", "form": [ { "kind": "input", "type": "rangeDateTimeCalendar", "path": "bookingSlots", "label": "Booking slots", "props": { "hint": "Pick a day, then a start and end time to add a slot", "minuteStep": 30 } } ]}Time Labels
Section titled “Time Labels”Use startTimeLabel and endTimeLabel to rename the headings on the two embedded time pickers (they default to Start time and End time).
import { gui } from '@golemui/gui-shared';
export default [ gui.inputs.rangeDateTimeCalendar('stay', { startTimeLabel: 'Check-in', endTimeLabel: 'Check-out', minuteStep: 30, label: 'Stay', defaultValue: [ { start: '2026-02-16T14:00:00', end: '2026-02-18T09:30:00', }, ], }),];{ "$schema": "https://golemui.com/schemas/form.schema.json", "form": [ { "kind": "input", "type": "rangeDateTimeCalendar", "path": "stay", "label": "Stay", "defaultValue": [ { "start": "2026-02-16T14:00:00", "end": "2026-02-18T09:30:00" } ], "props": { "startTimeLabel": "Check-in", "endTimeLabel": "Check-out", "minuteStep": 30 } } ]}Min and Max Date-Times
Section titled “Min and Max Date-Times”Use minDateTime and maxDateTime to bound the selectable instants (both inclusive). Days outside the range render disabled, and the time pickers are clamped on the boundary days. Customize (and localize) the out-of-bounds errors with minDateTimeMessage and maxDateTimeMessage.
import { gui } from '@golemui/gui-shared';
export default [ gui.inputs.rangeDateTimeCalendar('bookingSlots', { minDateTime: '2026-02-01T09:00:00', maxDateTime: '2026-07-31T18:00:00', minuteStep: 30, minDateTimeMessage: 'Bookings open on 1 Feb at 9:00', maxDateTimeMessage: 'Bookings close on 31 Jul at 18:00', label: 'Booking slots', }),];{ "$schema": "https://golemui.com/schemas/form.schema.json", "form": [ { "kind": "input", "type": "rangeDateTimeCalendar", "path": "bookingSlots", "label": "Booking slots", "props": { "minDateTime": "2026-02-01T09:00:00", "maxDateTime": "2026-07-31T18:00:00", "minuteStep": 30, "minDateTimeMessage": "Bookings open on 1 Feb at 9:00", "maxDateTimeMessage": "Bookings close on 31 Jul at 18:00" } } ]}Disabled Ranges
Section titled “Disabled Ranges”Use disabledRanges to grey out spans that cannot be part of a selection. A whole-day span runs 00:00:00–23:59:59 and disables the day on the grid; a partial-day span greys out the matching time slots and shows a disabled-count badge on that day. Selecting a range that overlaps a disabled span raises an input error whose message you set with disabledRangeMessage.
import { gui } from '@golemui/gui-shared';
export default [ gui.inputs.rangeDateTimeCalendar('bookingSlots', { minuteStep: 30, disabledRanges: [ { start: '2026-02-09T00:00:00', end: '2026-02-10T23:59:59', }, { start: '2026-02-17T13:00:00', end: '2026-02-17T14:00:00', }, ], disabledRangeMessage: 'That slot overlaps an unavailable time', label: 'Booking slots', }),];{ "$schema": "https://golemui.com/schemas/form.schema.json", "form": [ { "kind": "input", "type": "rangeDateTimeCalendar", "path": "bookingSlots", "label": "Booking slots", "props": { "minuteStep": 30, "disabledRanges": [ { "start": "2026-02-09T00:00:00", "end": "2026-02-10T23:59:59" }, { "start": "2026-02-17T13:00:00", "end": "2026-02-17T14:00:00" } ], "disabledRangeMessage": "That slot overlaps an unavailable time" } } ]}Allow Custom Time
Section titled “Allow Custom Time”Use the property allowCustomTime to let the user type a time directly in each picker, in addition to picking a slot. Typed times are still validated against minDateTime, maxDateTime and disabledRanges.
import { gui } from '@golemui/gui-shared';
export default [ gui.inputs.rangeDateTimeCalendar('bookingSlots', { allowCustomTime: true, minuteStep: 30, minDateTime: '2026-02-01T09:00:00', maxDateTime: '2026-07-31T18:00:00', label: 'Booking slots', }),];{ "$schema": "https://golemui.com/schemas/form.schema.json", "form": [ { "kind": "input", "type": "rangeDateTimeCalendar", "path": "bookingSlots", "label": "Booking slots", "props": { "allowCustomTime": true, "minuteStep": 30, "minDateTime": "2026-02-01T09:00:00", "maxDateTime": "2026-07-31T18:00:00" } } ]}Number of Months
Section titled “Number of Months”Use the property numberOfMonths to display multiple months side-by-side to ease selection of larger spans. The time pickers render on the first panel only.
import { gui } from '@golemui/gui-shared';
export default [ gui.inputs.rangeDateTimeCalendar('bookingSlots', { numberOfMonths: 2, minuteStep: 30, label: 'Booking slots', defaultValue: [ { start: '2026-02-16T14:00:00', end: '2026-02-18T09:30:00', }, ], }),];{ "$schema": "https://golemui.com/schemas/form.schema.json", "form": [ { "kind": "input", "type": "rangeDateTimeCalendar", "path": "bookingSlots", "label": "Booking slots", "defaultValue": [ { "start": "2026-02-16T14:00:00", "end": "2026-02-18T09:30:00" } ], "props": { "numberOfMonths": 2, "minuteStep": 30 } } ]}Day Count Labels
Section titled “Day Count Labels”A day carrying more than one range shows a count badge, and a day with disabled spans shows a disabled-count badge. Use dayCountAriaLabel and disabledDayCountAriaLabel to set the accessible label templates; the {count} token is substituted with the number of ranges.
import { gui } from '@golemui/gui-shared';
export default [ gui.inputs.rangeDateTimeCalendar('bookingSlots', { minuteStep: 30, disabledRanges: [ { start: '2026-02-17T13:00:00', end: '2026-02-17T14:00:00', }, ], dayCountAriaLabel: 'Number of slots taken: {count}', disabledDayCountAriaLabel: 'Number of blocked slots: {count}', label: 'Booking slots', defaultValue: [ { start: '2026-02-13T09:00:00', end: '2026-02-13T11:00:00', }, { start: '2026-02-13T13:00:00', end: '2026-02-13T15:00:00', }, { start: '2026-02-16T14:00:00', end: '2026-02-18T09:30:00', }, ], }),];{ "$schema": "https://golemui.com/schemas/form.schema.json", "form": [ { "kind": "input", "type": "rangeDateTimeCalendar", "path": "bookingSlots", "label": "Booking slots", "defaultValue": [ { "start": "2026-02-13T09:00:00", "end": "2026-02-13T11:00:00" }, { "start": "2026-02-13T13:00:00", "end": "2026-02-13T15:00:00" }, { "start": "2026-02-16T14:00:00", "end": "2026-02-18T09:30:00" } ], "props": { "minuteStep": 30, "disabledRanges": [ { "start": "2026-02-17T13:00:00", "end": "2026-02-17T14:00:00" } ], "dayCountAriaLabel": "Number of slots taken: {count}", "disabledDayCountAriaLabel": "Number of blocked slots: {count}" } } ]}Styling
Section titled “Styling”Range Date Time Calendars can be styled as explained in the Styling Guide. They inherit the day grid and range highlighting styles of the Range Calendar along with the time-list variables of the embedded pickers.
CSS Variables
Section titled “CSS Variables”Following you will find a list with the CSS Variables and a quick description of what you will style.
| CSS Variable | Description |
|---|---|
--gui-radius-md | Calendar container base radius |
--gui-radius-range | Border radius applied to the start and end of a selected range (calculated as double radius-md) |
--gui-bg-default | Calendar and time list container background color |
--gui-border-default | Default border color and cell hover border color |
--gui-space-3 | Internal padding |
--gui-calendar-change-month-button-width | Width of the next/prev buttons |
--gui-calendar-change-month-button-height | Height of the next/prev buttons |
--gui-text-default | Default text and focus border color |
--gui-text-secondary | Day button hover background color |
--gui-intent-primary-active | ”Today” border highlight color |
--gui-intent-primary | Selected range, selected day and selected time slot background color |
--gui-bg-disabled | Disabled day and disabled time slot background color |
--gui-time-list-height | Maximum height of each embedded time list viewport |
--gui-time-list-item-height | Height of each time slot row |
--gui-time-list-columns | Number of columns in the time slot grid |
Anatomy
Section titled “Anatomy”This is the anatomy of the Range Date Time Calendar Widget in case you want to use your CSS styles.
<gui-range-date-time-calendar class="gui-field"> <label for="fieldUid" id="fieldUid_calendar_label"> Label <div class="gui-calendar__hint" id="fieldUid_hint">Hint</div> </label>
<div class="gui-widget"> <div class="gui-calendar-input" role="group" aria-labelledby="fieldUid_calendar_label">
<!-- Committed date-time ranges, one pill each --> <gui-pills class="gui-range-calendar__pills"> <div class="gui-pills__list" role="list"> <div class="gui-pills__pill" role="listitem" tabindex="0" aria-label="Remove date 13/02/2026 9:00 AM - 13/02/2026 11:00 AM"> <span class="gui-pills__pill-text">13/02/2026 9:00 AM - 13/02/2026 11:00 AM</span> <button type="button" class="gui-pills__pill-remove" tabindex="-1">×</button> </div> </div> </gui-pills>
<div class="gui-calendar__container"> <button type="button" class="gui-button gui-calendar__month-button gui-calendar__month-button--prev" aria-label="Previous month"><svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 256 256"><path d="M181.66,133.66l-80,80a8,8,0,0,1-11.32-11.32L164.69,128,90.34,53.66a8,8,0,0,1,11.32-11.32l80,80A8,8,0,0,1,181.66,133.66Z"></path></svg></button>
<div class="gui-calendar__months-grid"> <div class="gui-calendar__panel"> <header class="gui-calendar__header"> <h2> <span class="gui-calendar__month-name">February</span> <button type="button" class="gui-calendar__year-selector" aria-expanded="false" aria-label="Select year"> <span class="gui-calendar__year-value">2026</span> <span class="gui-calendar__year-arrow" aria-hidden="true">▾</span> </button> </h2> </header>
<!-- Two embedded Time Pickers, below the header, on the first panel only. Each is disabled until its turn: the end picker unlocks once a start time is chosen. Opening a list takes over the days grid below. See the Time Picker anatomy: gui-time + arrow + gui-time-list. --> <div class="gui-range-date-time-calendar__time-row"> <div class="gui-range-date-time-calendar__time-column"> <gui-time-picker class="gui-time-picker gui-field gui-range-date-time-calendar__start"> <label class="gui-label">Start time</label> <!-- gui-time segments (hh:mm + AM/PM) and gui-time-list grid --> </gui-time-picker> </div> <div class="gui-range-date-time-calendar__time-column"> <gui-time-picker class="gui-time-picker gui-field gui-range-date-time-calendar__end" disabled> <label class="gui-label">End time</label> <!-- gui-time segments (hh:mm + AM/PM) and gui-time-list grid --> </gui-time-picker> </div> </div>
<div class="gui-calendar__days-grid" role="grid"> <div role="row" class="gui-calendar__rows"> <span class="gui-calendar__weekday" role="gridcell">Su</span> <span class="gui-calendar__weekday" role="gridcell">Mo</span> <span class="gui-calendar__weekday" role="gridcell">Tu</span> <span class="gui-calendar__weekday" role="gridcell">We</span> <span class="gui-calendar__weekday" role="gridcell">Th</span> <span class="gui-calendar__weekday" role="gridcell">Fr</span> <span class="gui-calendar__weekday" role="gridcell">Sa</span> </div> <div role="row" class="gui-calendar__rows"> <!-- A day carrying more than one range shows a count badge; a day with disabled spans shows a disabled-count badge (they stack when both). --> <button type="button" role="gridcell" class="gui-calendar__day-button" tabindex="-1" aria-selected="true">13<span class="gui-range-date-time-calendar__badges"><span class="gui-range-date-time-calendar__day-count" aria-label="2 ranges: ...">2</span></span></button> <button type="button" role="gridcell" class="gui-calendar__day-button" tabindex="-1" aria-selected="false">14</button> <button type="button" role="gridcell" class="gui-calendar__day-button" tabindex="-1" aria-selected="false">15</button> <button type="button" role="gridcell" class="gui-calendar__day-button range-start" tabindex="0" aria-selected="true">16</button> <button type="button" role="gridcell" class="gui-calendar__day-button in-range" tabindex="-1" aria-selected="true">17<span class="gui-range-date-time-calendar__badges"><span class="gui-range-date-time-calendar__disabled-count" aria-label="1 disabled ranges: ...">1</span></span></button> <button type="button" role="gridcell" class="gui-calendar__day-button range-end" tabindex="-1" aria-selected="true">18</button> <button type="button" role="gridcell" class="gui-calendar__day-button today" tabindex="-1" aria-selected="false">19</button> </div> </div> </div> </div>
<button type="button" class="gui-button gui-calendar__month-button gui-calendar__month-button--next" aria-label="Next month"><svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 256 256"><path d="M181.66,133.66l-80,80a8,8,0,0,1-11.32-11.32L164.69,128,90.34,53.66a8,8,0,0,1,11.32-11.32l80,80A8,8,0,0,1,181.66,133.66Z"></path></svg></button> </div> </div> </div>
<ul class="gui-validator" id="fieldUid_errors"> <li class="gui-validator__error" role="alert">Error message</li> </ul></gui-range-date-time-calendar>