Skip to content
Components, switch product

Date-time range

<gui-range-date-time> is a field to type ranges between two moments, each a date and a time. The user types the start and the end part by part, presses Enter, and the range becomes a pill. The field holds as many ranges as the user adds.

Code of the example above
RangeDateTimeBasic.tsx
import { GuiRangeDateTimeInput } from '@golemui/gui-components/react';
import '@golemui/gui-components/index.css';
export function RangeDateTimeBasic() {
return <GuiRangeDateTimeInput label="Maintenance window" />;
}

To pick the days in a calendar and the times from lists, use the Date-time range picker. For a single date and time, use the Date-time input.

The element’s entry point is @golemui/gui-components/range-date-time-input, which registers <gui-range-date-time> and the pills 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.

MaintenanceField.tsx
import { useState } from 'react';
import { GuiRangeDateTime } from '@golemui/gui-components/react';
import '@golemui/gui-components/index.css';
export function MaintenanceField() {
const [maintenance, setMaintenance] = useState([]);
return (
<GuiRangeDateTime
label="Maintenance window"
name="maintenance"
value={maintenance}
onGuiChange={(event) => setMaintenance(event.detail.value ?? [])}
/>
);
}

Properties

Property Type Default Description
localeId attribute locale-id string – BCP 47 locale for formatting and parsing, such as en-US or es.
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.
showHint attribute show-hint boolean true Whether the element renders its hint. Elements that embed it turn it off and show the hint themselves: aria-describedby still points at the hint by its id.
icon string '' Icon class name shown inside the control, for example from an icon font.
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.
hourFormat attribute hour-format HourFormat – 12- or 24-hour clock. Defaults to the locale's.
minuteStep attribute minute-step number 1 Minutes between the times offered in the list.
invalidDateMessage attribute invalid-date-message string – Error for a complete but impossible date, such as February 31.
value DateTimeRange[] [] The date-time ranges, as { start, end } ISO date-times.
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.
separator string – Text shown between the start and end of a range.
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.
incompleteMessage attribute incomplete-message string – Error when focus leaves a partly filled value.
allowEdit attribute allow-edit boolean false Opt-in select → edit → confirm flow on the pills.
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-focus One of the parts of the field received focus.
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.
gui-parts-change The typed parts changed before they form a complete value, for a host that mirrors them.
gui-edit-state-change Editing a range in place started, changed selection or ended. detail has the selected range and whether it is editing.
gui-range-click The user clicked a range pill. detail.range is the range.
gui-dropdown-toggle The count of ranges opened or closed its dropdown. detail.open is the new state.

CSS custom properties

Property Default Description
--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.

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.

Code of the example above
RangeDateTimeValue.tsx
import { GuiRangeDateTimeInput } from '@golemui/gui-components/react';
import type { DateTimeRange } from '@golemui/gui-components';
import '@golemui/gui-components/index.css';
const downtime: DateTimeRange[] = [
{ start: '2027-05-14T22:00:00', end: '2027-05-15T02:00:00' },
];
export function RangeDateTimeValue() {
return <GuiRangeDateTimeInput label="Server downtime" value={downtime} />;
}
  • An end typed before its start is swapped, so the range always runs forward. A range can span midnight and several days.
  • A range that overlaps or touches another one merges with it.
  • 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.

locale-id sets the order of the date parts, the clock and the format of the pills: German, below, writes the day first and uses a 24-hour clock. hour-format sets a 12 or 24-hour clock for any locale. See i18n.

Code of the example above
RangeDateTimeLocale.tsx
import { GuiRangeDateTimeInput } from '@golemui/gui-components/react';
import type { DateTimeRange } from '@golemui/gui-components';
import '@golemui/gui-components/index.css';
const maintenance: DateTimeRange[] = [
{ start: '2027-05-14T22:00:00', end: '2027-05-15T02:00:00' },
];
export function RangeDateTimeLocale() {
return (
<GuiRangeDateTimeInput
label="Wartungsfenster"
localeId="de-DE"
value={maintenance}
/>
);
}

On a 12-hour clock, the AM/PM part is a button: click it, or press Space, ↑ or ↓ on it, to switch. Typing stops at the minute: press → to reach the AM/PM part, and again to reach the end.

min-date-time and max-date-time limit both ends of a range, as ISO date-times. They are moments, not days: below, 8:00 AM on June 1 is too early, 10:00 AM is not.

Code of the example above
RangeDateTimeBounds.tsx
import { useState } from 'react';
import { GuiRangeDateTimeInput } from '@golemui/gui-components/react';
import '@golemui/gui-components/index.css';
export function RangeDateTimeBounds() {
const [errors, setErrors] = useState<string[]>([]);
return (
<GuiRangeDateTimeInput
id="launch-window"
label="Launch rehearsals"
hint="From 9:00 AM on June 1, 2027 to 6:00 PM on June 30, 2027."
minDateTime="2027-06-01T09:00:00"
maxDateTime="2027-06-30T18:00:00"
errors={errors}
onGuiInputError={(event) => setErrors([event.detail.message])}
onGuiInput={() => setErrors([])}
/>
);
}

When the field rejects a range, or a date like February 31, it 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 once the problem is fixed. Change the messages with min-date-time-message, max-date-time-message, invalid-date-message and incomplete-message.

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 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
RangeDateTimeEdit.tsx
import { GuiRangeDateTimeInput } from '@golemui/gui-components/react';
import type { DateTimeRange } from '@golemui/gui-components';
import '@golemui/gui-components/index.css';
const downtime: DateTimeRange[] = [
{ start: '2027-05-14T22:00:00', end: '2027-05-15T02:00:00' },
{ start: '2027-06-11T23:00:00', end: '2027-06-12T01:00:00' },
];
export function RangeDateTimeEdit() {
return (
<GuiRangeDateTimeInput label="Server downtime" allowEdit value={downtime} />
);
}

A disabled field can’t be focused or changed, and a form doesn’t submit it. A readonly field shows its ranges but doesn’t let the user add or remove one, and a form submits its value.

Code of the example above
RangeDateTimeDisabledReadonly.tsx
import { GuiRangeDateTimeInput } from '@golemui/gui-components/react';
import type { DateTimeRange } from '@golemui/gui-components';
import '@golemui/gui-components/index.css';
const holidays: DateTimeRange[] = [
{ start: '2027-12-20T18:00:00', end: '2028-01-02T09:00:00' },
];
export function RangeDateTimeDisabledReadonly() {
return (
<div className="gui-grid">
<div className="gui-grid__cell">
<GuiRangeDateTimeInput
label="Frozen deploys"
value={holidays}
disabled
/>
</div>
<div className="gui-grid__cell">
<GuiRangeDateTimeInput label="Booked slots" value={holidays} readOnly />
</div>
</div>
);
}

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

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

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

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

<gui-range-date-time> renders into the light DOM, so you can style every part with plain CSS. It has the same structure as the Date range field, with the gui-range-date-time-input block, and a time after the date in the start and in the end:

gui-range-date-time
<gui-range-date-time class="gui-field">
<span class="gui-label" id="window_label">
<span class="gui-label__text">Maintenance window</span>
</span>
<div class="gui-widget">
<div
role="group"
class="gui-widget-input gui-parts-ring gui-range-date-time-input"
aria-labelledby="window_label"
>
<gui-pills class="gui-range-date-time-input__pills">
<!-- One pill per range, as in gui-range-date -->
</gui-pills>
<div class="gui-range-date-time-input__inputs">
<div
class="gui-parts gui-range-date-time-input__field"
role="group"
aria-label="Start date-time"
>
<div
class="gui-parts__touch-target gui-range-date-time-input__touch-target"
>
<input
type="text"
inputmode="numeric"
role="spinbutton"
class="gui-parts__part gui-range-date-time-input__part"
placeholder="mm"
aria-label="Month"
/>
<div
class="gui-parts__visual-underline gui-range-date-time-input__visual-underline"
></div>
</div>
<span class="gui-range-date-time-input__separator">/</span>
<!-- The day and the year, then the separator ", " -->
<div
dir="ltr"
class="gui-parts__time-cluster gui-range-date-time-input__time-cluster"
>
<!-- The hour, ":" and the minute -->
</div>
<span class="gui-range-date-time-input__separator"> </span>
<div
class="gui-parts__touch-target gui-range-date-time-input__touch-target"
>
<button
type="button"
class="gui-parts__part gui-parts__dayperiod gui-range-date-time-input__part gui-range-date-time-input__dayperiod"
aria-label="AM/PM"
>
AM
</button>
<div
class="gui-parts__visual-underline gui-range-date-time-input__visual-underline"
></div>
</div>
</div>
<span class="gui-range-date-time-input__separator" aria-hidden="true">
-
</span>
<div
class="gui-parts gui-range-date-time-input__field"
role="group"
aria-label="End date-time"
>
<!-- The same parts as the start -->
</div>
</div>
</div>
</div>
</gui-range-date-time>
ClassPart
gui-fieldThe element itself. Lines its parts up with the fields next to it in a gui-grid row.
gui-widget-input, gui-parts-ringThe part that looks like a field. gui-parts-ring draws its focus ring and its error border.
gui-range-date-time-inputThe field of this element. gui-range-date-time-input--icon is on it when there is an icon.
gui-range-date-time-input__pillsThe pills, a <gui-pills> element. gui-pills__pill is one range.
gui-range-date-time-input__fieldThe start, or the end: a gui-parts group of parts.
gui-range-date-time-input__partOne part. gui-range-date-time-input__year is the year and __dayperiod the AM/PM button.
gui-range-date-time-input__time-clusterThe hour and minute, which keep their left-to-right order in a right-to-left page.
gui-range-date-time-input__separatorThe text between the parts, and between the start and the end (separator, - by default).
gui-range-date-time-input__visual-underlineThe line under the focused part.

The label, hint, icon, pills and errors have the classes of the Date range field. 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 same design tokens as the Date range field: --gui-widget-size for its height, --gui-bg-surface, --gui-border-default and --gui-radius-md for its box, --gui-border-focus, --gui-shadow-focus, --gui-intent-error and --gui-shadow-focus-error for its states, and --gui-intent-primary with the --gui-pill-* tokens for its pills.

styles.css
/* A brand focus color in one form only. */
.operations {
--gui-border-focus: #d96c3f;
}

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

  • Name. The label names the field, a group with the start and the end. Without a label the field has no accessible name, and development builds log a warning.
  • Start and end. The start and the end are groups named “Start date-time” and “End date-time”. Change the names with start-date-time-aria-label and end-date-time-aria-label; an empty value removes them.
  • Parts. Each part is a spin button named “Day”, “Month”, “Year”, “Hour” or “Minute”, with its minimum, maximum and current value, and the AM/PM part is a button named “AM/PM”. Change the names with day-aria-label, month-aria-label, year-aria-label, hour-aria-label, minute-aria-label and day-period-aria-label.
  • Pills. The pills are a toolbar named “Selected date-time ranges”. Each pill is a button named by its range, described as “Remove date-time”: change that with remove-pill-aria-label.
  • Description and errors. The hint is the field’s description. While errors show, the field has aria-invalid="true" and points at them with aria-errormessage, and screen readers announce new errors as they appear.
  • Required. required sets aria-required on every part; the visual marker is hidden from screen readers.

In the parts:

KeyAction
TabMoves focus to the first part of the start, and away from the field. The other parts are not tab stops.
DigitsFill the part. A full part moves on to the next empty part, from the start to the end.
← →Move between the parts, and between the start and the end. ← on the first part moves to the last pill.
↑ ↓Increase or decrease the part, or switch AM and PM.
Backspace DeleteClear the part. On an empty part, while every part is empty, they move to the last pill.
EnterAdds the range typed, or saves the range being edited.

On a pill, the arrows, Home and End move between the pills, and Delete removes the range, as in the Date range field.

What every element shares, such as translating the default names, is in Accessibility.