Skip to content
Components, switch product

Date-time range picker

<gui-range-date-time-picker> is a field for ranges between two moments, with a popup to pick them: a calendar for the days, and a start and an end time. The user can also type the range. Each range becomes a pill, and the field holds as many as the user adds.

Code of the example above
RangeDateTimePickerBasic.tsx
import { GuiRangeDateTimePicker } from '@golemui/gui-components/react';
import '@golemui/gui-components/index.css';
export function RangeDateTimePickerBasic() {
return <GuiRangeDateTimePicker label="Conference sessions" minuteStep={30} />;
}

It combines a Date-time range field and a Date-time range calendar. For a single date and time, use the Date-time picker.

The element’s entry point is @golemui/gui-components/range-date-time-picker, which registers <gui-range-date-time-picker> and the elements it renders. To install the package and load the stylesheet, see Installation.

The @golemui/gui-components/react components have typed props and an on* prop for each event. React 18 or later.

SessionsField.tsx
import { useState } from 'react';
import { GuiRangeDateTimePicker } from '@golemui/gui-components/react';
import '@golemui/gui-components/index.css';
export function SessionsField() {
const [sessions, setSessions] = useState([]);
return (
<GuiRangeDateTimePicker
label="Conference sessions"
name="sessions"
value={sessions}
onGuiChange={(event) => setSessions(event.detail.value ?? [])}
/>
);
}

Properties

Property Type Default Description
icon string '' Icon class name shown inside the control, for example from an icon font.
showErrors attribute show-errors boolean true Whether the element renders its own error list. Elements that embed it turn it off and show the errors themselves.
localeId attribute locale-id string – BCP 47 locale for formatting and parsing, such as en-US or es.
value DateTimeRange[] [] The date-time ranges, as { start, end } ISO date-times.
toggleAriaLabel attribute toggle-aria-label string – Accessible name of the button that opens the popup. An empty value keeps the default.
dayAriaLabel attribute day-aria-label string – Accessible name of the day part. An empty value keeps the default.
monthAriaLabel attribute month-aria-label string – Accessible name of the month part. An empty value keeps the default.
yearAriaLabel attribute year-aria-label string – Accessible name of the year part. An empty value keeps the default.
hourAriaLabel attribute hour-aria-label string – Accessible name of the hour part. An empty value keeps the default.
minuteAriaLabel attribute minute-aria-label string – Accessible name of the minute part. An empty value keeps the default.
dayPeriodAriaLabel attribute day-period-aria-label string – Accessible name of the AM/PM part. An empty value keeps the default.
separator string – Text shown between the start and end of a range.
removePillAriaLabel attribute remove-pill-aria-label string – Accessible name of the remove button of each range pill. An empty value keeps the default.
startDateTimeAriaLabel attribute start-date-time-aria-label string – Accessible name of the start date-time field. An empty value removes it.
endDateTimeAriaLabel attribute end-date-time-aria-label string – Accessible name of the end date-time field. An empty value removes it.
invalidDateMessage attribute invalid-date-message string – Error for a complete but impossible date, such as February 31.
prevMonthIcon attribute prev-month-icon string '' Icon class name of the previous-month button.
nextMonthIcon attribute next-month-icon string '' Icon class name of the next-month button.
prevMonthAriaLabel attribute prev-month-aria-label string – Accessible name of the previous-month button. An empty value keeps the default.
nextMonthAriaLabel attribute next-month-aria-label string – Accessible name of the next-month button. An empty value keeps the default.
selectYearAriaLabel attribute select-year-aria-label string – Accessible name of the button that opens the year grid. An empty value keeps the default.
yearGridAriaLabel attribute year-grid-aria-label string – Accessible name of the year grid. An empty value keeps the default.
dayFormat attribute day-format 'numeric' | '2-digit' – How day numbers are written.
weekdayFormat attribute weekday-format 'short' | 'long' | 'narrow' – How weekday names are written in the header.
monthFormat attribute month-format 'numeric' | '2-digit' | 'long' | 'short' | 'narrow' – How the month is written in the header.
numberOfMonths attribute number-of-months number – Number of months shown side by side.
hourFormat attribute hour-format HourFormat – 12- or 24-hour clock. Defaults to the locale's.
minuteStep attribute minute-step number – Minutes between the times offered in the list.
allowCustomTime attribute allow-custom-time boolean false Allows typing any time, not only picking one from the list.
startTimeLabel attribute start-time-label string – Label of the start time. An empty value keeps the default.
endTimeLabel attribute end-time-label string – Label of the end time. An empty value keeps the default.
minDateTime attribute min-date-time string – Earliest allowed date-time, as an ISO date-time (YYYY-MM-DDTHH:mm:ss).
maxDateTime attribute max-date-time string – Latest allowed date-time, as an ISO date-time (YYYY-MM-DDTHH:mm:ss).
minDateTimeMessage attribute min-date-time-message string – Error for a date-time before minDateTime.
maxDateTimeMessage attribute max-date-time-message string – Error for a date-time after maxDateTime.
disabledRanges attribute disabled-ranges DateTimeRange[] – Date-times that cannot be picked, as { start, end } ISO date-time ranges.
disabledRangeMessage attribute disabled-range-message string – Error for a time inside disabledRanges.
noAvailableTimesMessage attribute no-available-times-message string – Text shown when no time can be picked.
incompleteMessage attribute incomplete-message string – Error when focus leaves a partly filled value.
dayCountAriaLabel attribute day-count-aria-label string – Read with a day that has several ranges, before the ranges themselves. {count} is the number of ranges.
disabledDayCountAriaLabel attribute disabled-day-count-aria-label string – Read with a day that has disabled times, before the times themselves. {count} is the number of disabled ranges.
allowEdit attribute allow-edit boolean false Lets the user edit a range in place from its pill.
editLabel attribute edit-label string – Tooltip of the edit button of a range pill.
editAriaLabel attribute edit-aria-label string – Hint that a range pill can be edited. {label} is the range.
confirmEditLabel attribute confirm-edit-label string – Tooltip of the confirm button of a range being edited.
cancelEditLabel attribute cancel-edit-label string – Tooltip of the cancel button of a range being edited.
editStartedMessage attribute edit-started-message string – Announcement when editing a range starts. {label} is the range.
editCommittedMessage attribute edit-committed-message string – Announcement when an edited range is saved. {label} is the new range.
editCancelledMessage attribute edit-cancelled-message string – Announcement when editing a range is cancelled.
uid string – The id of the element's control. The ids of its label, hint and errors derive from it. Optional: when it is not set, the element generates one from its tag name and a page-wide counter, e.g. gui-textinput-3. Generated ids depend on creation order, so a server-rendered element must set uid or its ids will not match after hydration.

Form control properties

Every field has these, from the base class of the form controls.

Property Type Default Description
name string – The name the value is submitted under. Without it the element stays out of its form. Reflected, like a native control's: the form reads a single value's name from the attribute.
label string – The visible label, which also names the control for assistive technology. Without one the control has no accessible name; the date, time, range, tags and file fields warn about it in development builds.
hint string – Help text shown under the label and announced as the control's description.
errors string[] [] Error messages to show, typically from your own validation. They show as soon as they are set, unless touched is false.
touched boolean – Whether the user has interacted with the control. Leave it unset unless you validate on interaction: false holds errors back until it becomes true.
required boolean false The control needs a value: it is marked as required and, with a name, blocks submission.
readOnly attribute readonly boolean – The value can be read and focused but not changed.
disabled boolean – Disabled by its own attribute or by a disabled <fieldset> around it. Setting the property sets the attribute, like a native control's.
form read-only, no attribute HTMLFormElement | null – The <form> the element belongs to, if any.
validity read-only, no attribute ValidityState – The element's validity, like a native control's.
validationMessage read-only, no attribute string – The message the browser shows for an invalid value.

Methods

Method Description
checkValidity(): boolean –
reportValidity(): boolean –

Events

Every event bubbles and is composed, so you can listen on any ancestor.

Event Description
gui-input The user changed the value. detail.value is the new value.
gui-change The user added, removed or finished editing a range. detail.value is the list of ranges.
gui-blur Focus left the control.
gui-input-error The element rejected what the user entered, such as an impossible date or a value out of bounds. detail.message is the error; show it through errors.

CSS custom properties

Property Default Description
--gui-calendar-width – Width of one month.
--gui-calendar-day-button-size – Size of each day.
--gui-calendar-change-month-button-width – Width of the previous- and next-month buttons.
--gui-calendar-change-month-button-height – Height of the previous- and next-month buttons.
--gui-calendar-year-button-width – Width of each year in the year grid.
--gui-calendar-year-button-height – Height of each year in the year grid.
--gui-calendar-year-grid-height – Height of the year grid.
--gui-calendar-time-grid-height – Height of the time grid.
--gui-calendar-time-button-height – Height of each time in the grid.
--gui-range-bg – Background of the days inside a selected range.
--gui-range-error-bg – Background of the days inside a rejected range.
--gui-pill-height – Height of each pill.
--gui-pill-font-size – Font size of the pill text.
--gui-pill-action-size – Size of the icons inside a pill.
--gui-pill-action-hit – Clickable area of the buttons inside a pill.

The popup opens when a part of the field gets focus, or from the button at the end of the field. A range needs four pieces, which the user can pick in any order:

  1. The days. The first day picked in the calendar is one end of the span, and the second day completes it, before or after the first. Picking the same day twice makes a span of one day.
  2. The start and end times. Above the days, a start time and an end time each open a list of times, which takes the place of the days while it is open. minute-step sets the minutes between the times.

With the four pieces, the range becomes a pill and the popup stays open for the next one. Each piece also fills the parts of the field, and a range typed in the field shows in the popup.

By default the times are picked from the lists. allow-custom-time lets the user type any time in the start and end times of the popup. start-time-label and end-time-label change their labels.

value is a list of ranges. Each range is an object with a start and an end, both local ISO date-times (YYYY-MM-DDTHH:mm:ss), with no time zone. As an attribute, value is a JSON array. The end always comes after the start, and a range can span several days.

A range that overlaps or touches another one merges with it, and the pills show in time order. gui-change fires each time the user adds or removes a range, with the whole list in event.detail.value.

min-date-time and max-date-time limit both ends of a range, as ISO date-times. The days before and after them are disabled, and on their own days the time lists start or stop at their time.

disabled-ranges takes a JSON list of { start, end } date-time ranges the user can’t book:

  • A day the disabled ranges cover whole is disabled in the calendar.
  • A day they cover in part shows a count of its disabled ranges; hover the count to see their times. On that day, the time lists disable those times.
  • A range that overlaps a disabled range is rejected, and so is a span of days that steps over a disabled day.
Code of the example above
RangeDateTimePickerBounds.tsx
import { useState } from 'react';
import { GuiRangeDateTimePicker } from '@golemui/gui-components/react';
import type { DateTimeRange } from '@golemui/gui-components';
import '@golemui/gui-components/index.css';
const closed: DateTimeRange[] = [
{ start: '2027-03-10T00:00:00', end: '2027-03-10T23:59:59' },
{ start: '2027-03-15T12:00:00', end: '2027-03-15T14:00:00' },
];
export function RangeDateTimePickerBounds() {
const [value, setValue] = useState<DateTimeRange[] | undefined>([
{ start: '2027-03-03T09:00:00', end: '2027-03-04T17:00:00' },
]);
const [errors, setErrors] = useState<string[]>([]);
return (
<GuiRangeDateTimePicker
id="venue-booking"
label="Venue booking"
hint="Open from 8:00 AM on March 1 to 10:00 PM on March 31. Closed all day on March 10, and from 12:00 PM to 2:00 PM on March 15."
minuteStep={30}
minDateTime="2027-03-01T08:00:00"
maxDateTime="2027-03-31T22:00:00"
disabledRanges={closed}
value={value}
errors={errors}
onGuiInputError={(event) => setErrors([event.detail.message])}
onGuiInput={(event) => {
setValue(event.detail.value);
setErrors([]);
}}
/>
);
}

Pick March 3, then March 17, above: the span steps over March 10, so the picker shows it in red and fires gui-input-error with the message in event.detail.message. It doesn’t show the message itself: the example sets it as errors and clears it on the next gui-input, which fires when the user adds a range. Change the messages with disabled-range-message, min-date-time-message, max-date-time-message and incomplete-message.

number-of-months shows several months side by side. day-format, weekday-format and month-format change how the calendar writes the days, the weekday names and the month, and locale-id sets its language and the format of the field and the pills. See i18n.

allow-edit lets the user change a range they already added. Select a pill, then press F2 or E, or use its edit button: the range loads into the parts and the calendar, and the pill shows the change as the user types. Enter or the confirm button saves it, and Escape or the cancel button keeps the old range. Screen readers announce each step.

Code of the example above
RangeDateTimePickerEdit.tsx
import { GuiRangeDateTimePicker } from '@golemui/gui-components/react';
import type { DateTimeRange } from '@golemui/gui-components';
import '@golemui/gui-components/index.css';
const sessions: DateTimeRange[] = [
{ start: '2027-03-03T09:00:00', end: '2027-03-03T12:30:00' },
{ start: '2027-03-04T14:00:00', end: '2027-03-04T17:00:00' },
];
export function RangeDateTimePickerEdit() {
return (
<GuiRangeDateTimePicker
label="Conference sessions"
minuteStep={30}
allowEdit
value={sessions}
/>
);
}

With a name, <gui-range-date-time-picker> submits one entry per range under that name. Each entry is the range as JSON, such as {"start":"2027-03-03T09:00:00","end":"2027-03-04T17:00:00"}. Read them all with FormData.getAll(). required blocks the submit while the field has no range.

Code of the example above
RangeDateTimePickerNativeForm.tsx
import { useState } from 'react';
import {
GuiButton,
GuiRangeDateTimePicker,
} from '@golemui/gui-components/react';
import '@golemui/gui-components/index.css';
export function RangeDateTimePickerNativeForm() {
const [result, setResult] = useState('');
return (
<form
id="charter-form"
onSubmit={(event) => {
event.preventDefault();
const ranges = new FormData(event.currentTarget).getAll('charter');
setResult(`Submitted ${ranges.join(', ')}`);
}}
>
<div className="gui-grid">
<div className="gui-grid__cell">
<GuiRangeDateTimePicker
label="Boat charter"
name="charter"
minuteStep={60}
required
/>
</div>
<div className="gui-grid__cell">
<GuiButton type="submit" label="Reserve" />
</div>
</div>
<p role="status">{result}</p>
</form>
);
}

A range that starts before min-date-time, ends after max-date-time or overlaps disabled-ranges blocks the submit too, even one you set from code. The browser shows the same message the picker gives the user, such as the disabled-range-message.

How the elements submit, validate and reset is in Native forms.

<gui-range-date-time-picker> renders into the light DOM, so you can style every part with plain CSS. It renders a <gui-range-date-time> field, a toggle button and, while open, a panel with a <gui-range-date-time-calendar>:

gui-range-date-time-picker
<gui-range-date-time-picker>
<span class="gui-label" id="sessions_label">
<span class="gui-label__text">Conference sessions</span>
</span>
<div class="gui-widget">
<gui-range-date-time id="sessions_date" class="gui-field">
<!-- The field: see the anatomy on the Date-time range page -->
</gui-range-date-time>
<button
type="button"
class="gui-range-date-time-picker__arrow"
aria-haspopup="dialog"
aria-label="Show calendar"
aria-expanded="true"
aria-controls="sessions_popup"
>
<span class="gui-caret" aria-hidden="true"></span>
</button>
<div class="gui-picker__panel">
<gui-range-date-time-calendar
role="dialog"
id="sessions_popup"
aria-labelledby="sessions_label"
class="gui-field"
>
<!-- The calendar: see the anatomy on the Date-time range calendar page -->
</gui-range-date-time-calendar>
</div>
</div>
<div
class="gui-validator gui-validator--empty"
id="sessions_errors"
role="alert"
></div>
</gui-range-date-time-picker>
ClassPart
gui-label, gui-label__textThe label, and its text with the required marker.
gui-widgetThe box around the field, the button and the panel.
gui-range-date-time-picker__arrowThe button that opens and closes the popup. Its gui-caret turns while the popup is open.
gui-picker__panelThe popup. While there are errors, it repeats them at its bottom.
gui-validatorThe errors container. gui-validator--empty is on it while there are no errors.

The field inside is a <gui-range-date-time> and the calendar a <gui-range-date-time-calendar>: their classes style the picker too. The ids derive from uid, or from a generated id when you don’t set one. Select by class or by tag, not by id.

The field reads the tokens of the Date range field, and the calendar those of the Date-time range calendar. The popup reads these:

TokenWhat it styles
--gui-bg-defaultThe background of the popup.
--gui-border-defaultThe border of the popup.
--gui-radius-mdThe corner radius of the popup.
--gui-shadow-mdThe shadow of the popup.
--gui-space-2The padding of the popup.
--gui-intent-errorThe popup’s border while invalid.
--gui-text-defaultThe toggle button’s caret.
styles.css
/* A taller time grid in the popup. */
gui-range-date-time-picker {
--gui-calendar-time-grid-height: 280px;
}

To restyle beyond the tokens, see Overriding styles; for dark mode and the Clay theme, see Themes and dark mode. To change the caret, see Carets.

  • Name. The label names the field and the popup. Without a label, the field has no accessible name and the popup is named “Calendar”.
  • Toggle button. The button that opens the popup is named “Show calendar”; change it with toggle-aria-label. It has aria-haspopup="dialog", and aria-expanded tells whether the popup is open.
  • Popup. The popup is a dialog with the start and end times and the days. Each day is named by its full date, and a day with ranges or disabled times describes them, such as “2 ranges” followed by the ranges. Change those counts with day-count-aria-label and disabled-day-count-aria-label. The rest is as in the Date-time range calendar.
  • Start, end and parts. As in the Date-time range field: the start and end are named “Start date-time” and “End date-time”, and each part by its type.
KeyAction
TabMoves focus to the field’s first part, then to the toggle button, then into the open popup. Leaving the picker closes it.
Focus on a partOpens the popup, and keeps the focus in the field so the user can type.
Enter SpaceOn the toggle button, opens the popup and moves focus to a day, or closes it.
← → ↑ ↓In the calendar, move by a day or a week, into the next or previous month at the edges, skipping disabled days.
Page Up Page Down, in the calendarMove to the same day in the previous or next month (the last day of a shorter month), or the nearest day the user can pick. With Shift, the same day in the previous or next year.
Home End, in the calendarMove to the first or last day of the week the user can pick, within the month.
Enter SpaceIn the calendar, picks the focused day: the first pick starts the span, the second completes it.
EscapeCloses the popup and moves focus back to the field. In a time list or the year grid, closes it first.

The keys of the field and its pills are on the Date-time range page, and those of the time lists on the Time picker page. What every element shares, such as translating the default names, is in Accessibility.