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.
import { GuiRangeDateTimeInput } from '@golemui/gui-components/react';import '@golemui/gui-components/index.css';
export function RangeDateTimeBasic() { return <GuiRangeDateTimeInput label="Maintenance window" />;}import { Component, CUSTOM_ELEMENTS_SCHEMA } from '@angular/core';import '@golemui/gui-components/range-date-time-input';
@Component({ selector: 'app-range-date-time-basic', schemas: [CUSTOM_ELEMENTS_SCHEMA], template: `<gui-range-date-time label="Maintenance window" ></gui-range-date-time>`,})export class RangeDateTimeBasicComponent {}<script setup lang="ts">import '@golemui/gui-components/range-date-time-input';import '@golemui/gui-components/index.css';</script>
<template> <gui-range-date-time label="Maintenance window"></gui-range-date-time></template>import { LitElement, html } from 'lit';import { customElement } from 'lit/decorators.js';import '@golemui/gui-components/range-date-time-input';import '@golemui/gui-components/index.css';
@customElement('range-date-time-basic')export class RangeDateTimeBasic extends LitElement { protected override createRenderRoot() { return this; }
override render() { return html` <gui-range-date-time label="Maintenance window"></gui-range-date-time> `; }}<gui-range-date-time label="Maintenance window"></gui-range-date-time>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.
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 ?? [])} /> );}Add CUSTOM_ELEMENTS_SCHEMA to the component that uses the tags, and the stylesheet to styles in angular.json.
import { Component, CUSTOM_ELEMENTS_SCHEMA } from '@angular/core';import type { GuiEventMap, GuiRangeDateTimeEvents } from '@golemui/gui-components';import '@golemui/gui-components/range-date-time-input';
type ChangeEvent = GuiEventMap<typeof GuiRangeDateTimeEvents>['gui-change'];
@Component({ selector: 'app-maintenance-field', schemas: [CUSTOM_ELEMENTS_SCHEMA], template: ` <gui-range-date-time label="Maintenance window" name="maintenance" [value]="maintenance" (gui-change)="onChange($event)" ></gui-range-date-time> `,})export class MaintenanceFieldComponent { maintenance = [];
onChange(event: Event) { this.maintenance = (event as ChangeEvent).detail.value ?? []; }}"styles": ["node_modules/@golemui/gui-components/lib/styles/index.css"]Tell Vue's compiler that gui-* tags are custom elements, and add the typings once for typed props and @gui-* handlers.
<script setup lang="ts">import { ref } from 'vue';import '@golemui/gui-components/range-date-time-input';import '@golemui/gui-components/index.css';
const maintenance = ref([]);</script>
<template> <gui-range-date-time label="Maintenance window" name="maintenance" :value="maintenance" @gui-change="maintenance = $event.detail.value ?? []" /></template>vue({ template: { compilerOptions: { isCustomElement: (tag) => tag.startsWith('gui-') } },});import type {} from '@golemui/gui-components/vue';The page's stylesheet styles the gui-* elements, so it only reaches them outside a shadow root. Render the host component into the light DOM too, as below, or load the stylesheet into its shadow root.
import { LitElement, html } from 'lit';import { customElement, state } from 'lit/decorators.js';import '@golemui/gui-components/range-date-time-input';import '@golemui/gui-components/index.css';
@customElement('maintenance-field')export class MaintenanceField extends LitElement { @state() private maintenance = [];
protected override createRenderRoot() { return this; }
override render() { return html` <gui-range-date-time label="Maintenance window" name="maintenance" .value=${this.maintenance} @gui-change=${(event: CustomEvent) => (this.maintenance = event.detail.value)} ></gui-range-date-time> `; }}With any bundler, such as Vite. Import the element once, anywhere, and use the tag in your markup.
<gui-range-date-time label="Maintenance window" name="maintenance"></gui-range-date-time>
<script type="module" src="./main.js"></script>import '@golemui/gui-components/range-date-time-input';import '@golemui/gui-components/index.css';
const element = document.querySelector('gui-range-date-time');
element.addEventListener('gui-change', (event) => { console.log(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. |
Examples
Section titled “Examples”The value
Section titled “The value”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.
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} />;}import { Component, CUSTOM_ELEMENTS_SCHEMA } from '@angular/core';import type { DateTimeRange } from '@golemui/gui-components';import '@golemui/gui-components/range-date-time-input';
@Component({ selector: 'app-range-date-time-value', schemas: [CUSTOM_ELEMENTS_SCHEMA], template: ` <gui-range-date-time label="Server downtime" [value]="downtime" ></gui-range-date-time> `,})export class RangeDateTimeValueComponent { downtime: DateTimeRange[] = [ { start: '2027-05-14T22:00:00', end: '2027-05-15T02:00:00' }, ];}<script setup lang="ts">import type { DateTimeRange } from '@golemui/gui-components';import '@golemui/gui-components/range-date-time-input';import '@golemui/gui-components/index.css';
const value: DateTimeRange[] = [ { start: '2027-05-14T22:00:00', end: '2027-05-15T02:00:00' },];</script>
<template> <gui-range-date-time label="Server downtime" :value.prop="value" ></gui-range-date-time></template>import { LitElement, html } from 'lit';import { customElement } from 'lit/decorators.js';import type { DateTimeRange } from '@golemui/gui-components';import '@golemui/gui-components/range-date-time-input';import '@golemui/gui-components/index.css';
const downtime: DateTimeRange[] = [ { start: '2027-05-14T22:00:00', end: '2027-05-15T02:00:00' },];
@customElement('range-date-time-value')export class RangeDateTimeValue extends LitElement { protected override createRenderRoot() { return this; }
override render() { return html` <gui-range-date-time label="Server downtime" .value=${downtime} ></gui-range-date-time> `; }}<gui-range-date-time label="Server downtime" value='[{"start":"2027-05-14T22:00:00","end":"2027-05-15T02:00:00"}]'></gui-range-date-time>- 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 and clock
Section titled “Locale and clock”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.
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} /> );}import { Component, CUSTOM_ELEMENTS_SCHEMA } from '@angular/core';import type { DateTimeRange } from '@golemui/gui-components';import '@golemui/gui-components/range-date-time-input';
@Component({ selector: 'app-range-date-time-locale', schemas: [CUSTOM_ELEMENTS_SCHEMA], template: ` <gui-range-date-time label="Wartungsfenster" locale-id="de-DE" [value]="window" ></gui-range-date-time> `,})export class RangeDateTimeLocaleComponent { window: DateTimeRange[] = [ { start: '2027-05-14T22:00:00', end: '2027-05-15T02:00:00' }, ];}<script setup lang="ts">import type { DateTimeRange } from '@golemui/gui-components';import '@golemui/gui-components/range-date-time-input';import '@golemui/gui-components/index.css';
const value: DateTimeRange[] = [ { start: '2027-05-14T22:00:00', end: '2027-05-15T02:00:00' },];</script>
<template> <gui-range-date-time label="Wartungsfenster" locale-id="de-DE" :value.prop="value" ></gui-range-date-time></template>import { LitElement, html } from 'lit';import { customElement } from 'lit/decorators.js';import type { DateTimeRange } from '@golemui/gui-components';import '@golemui/gui-components/range-date-time-input';import '@golemui/gui-components/index.css';
const maintenance: DateTimeRange[] = [ { start: '2027-05-14T22:00:00', end: '2027-05-15T02:00:00' },];
@customElement('range-date-time-locale')export class RangeDateTimeLocale extends LitElement { protected override createRenderRoot() { return this; }
override render() { return html` <gui-range-date-time label="Wartungsfenster" locale-id="de-DE" .value=${maintenance} ></gui-range-date-time> `; }}<gui-range-date-time label="Wartungsfenster" locale-id="de-DE" value='[{"start":"2027-05-14T22:00:00","end":"2027-05-15T02:00:00"}]'></gui-range-date-time>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.
Bounds
Section titled “Bounds”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.
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([])} /> );}import { Component, CUSTOM_ELEMENTS_SCHEMA, signal } from '@angular/core';import type { GuiEventMap, GuiRangeDateTimeInputEvents,} from '@golemui/gui-components';import '@golemui/gui-components/range-date-time-input';
type RangeDateTimeEvent<K extends keyof typeof GuiRangeDateTimeInputEvents> = GuiEventMap<typeof GuiRangeDateTimeInputEvents>[K];
@Component({ selector: 'app-range-date-time-bounds', schemas: [CUSTOM_ELEMENTS_SCHEMA], template: ` <gui-range-date-time id="launch-window" label="Launch rehearsals" hint="From 9:00 AM on June 1, 2027 to 6:00 PM on June 30, 2027." min-date-time="2027-06-01T09:00:00" max-date-time="2027-06-30T18:00:00" [errors]="errors()" (gui-input-error)="onInputError($event)" (gui-input)="errors.set([])" ></gui-range-date-time> `,})export class RangeDateTimeBoundsComponent { errors = signal<string[]>([]);
onInputError(event: Event) { this.errors.set([ (event as RangeDateTimeEvent<'gui-input-error'>).detail.message, ]); }}<script setup lang="ts">import { ref } from 'vue';import '@golemui/gui-components/range-date-time-input';import '@golemui/gui-components/index.css';
const errors = ref<string[]>([]);</script>
<template> <gui-range-date-time id="launch-window" label="Launch rehearsals" hint="From 9:00 AM on June 1, 2027 to 6:00 PM on June 30, 2027." min-date-time="2027-06-01T09:00:00" max-date-time="2027-06-30T18:00:00" :errors.prop="errors" @gui-input-error="errors = [$event.detail.message]" @gui-input="errors = []" ></gui-range-date-time></template>import { LitElement, html } from 'lit';import { customElement, state } from 'lit/decorators.js';import type { GuiEventMap, GuiRangeDateTimeInputEvents,} from '@golemui/gui-components';import '@golemui/gui-components/range-date-time-input';import '@golemui/gui-components/index.css';
type RangeDateTimeEvent<K extends keyof typeof GuiRangeDateTimeInputEvents> = GuiEventMap<typeof GuiRangeDateTimeInputEvents>[K];
@customElement('range-date-time-bounds')export class RangeDateTimeBounds extends LitElement { @state() private errors: string[] = [];
protected override createRenderRoot() { return this; }
private onInputError(event: RangeDateTimeEvent<'gui-input-error'>) { this.errors = [event.detail.message]; }
private onInput() { this.errors = []; }
override render() { return html` <gui-range-date-time id="launch-window" label="Launch rehearsals" hint="From 9:00 AM on June 1, 2027 to 6:00 PM on June 30, 2027." min-date-time="2027-06-01T09:00:00" max-date-time="2027-06-30T18:00:00" .errors=${this.errors} @gui-input-error=${this.onInputError} @gui-input=${this.onInput} ></gui-range-date-time> `; }}<gui-range-date-time id="launch-window" label="Launch rehearsals" hint="From 9:00 AM on June 1, 2027 to 6:00 PM on June 30, 2027." min-date-time="2027-06-01T09:00:00" max-date-time="2027-06-30T18:00:00"></gui-range-date-time>
<script type="module"> const launch = document.querySelector('#launch-window');
launch.addEventListener('gui-input-error', (event) => { launch.errors = [event.detail.message]; }); launch.addEventListener('gui-input', () => { launch.errors = []; });</script>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.
Editing ranges
Section titled “Editing ranges”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.
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} /> );}import { Component, CUSTOM_ELEMENTS_SCHEMA } from '@angular/core';import type { DateTimeRange } from '@golemui/gui-components';import '@golemui/gui-components/range-date-time-input';
@Component({ selector: 'app-range-date-time-edit', schemas: [CUSTOM_ELEMENTS_SCHEMA], template: ` <gui-range-date-time label="Server downtime" allow-edit [value]="downtime" ></gui-range-date-time> `,})export class RangeDateTimeEditComponent { 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' }, ];}<script setup lang="ts">import type { DateTimeRange } from '@golemui/gui-components';import '@golemui/gui-components/range-date-time-input';import '@golemui/gui-components/index.css';
const value: 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' },];</script>
<template> <gui-range-date-time label="Server downtime" :allow-edit="true" :value.prop="value" ></gui-range-date-time></template>import { LitElement, html } from 'lit';import { customElement } from 'lit/decorators.js';import type { DateTimeRange } from '@golemui/gui-components';import '@golemui/gui-components/range-date-time-input';import '@golemui/gui-components/index.css';
const downtimes: 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' },];
@customElement('range-date-time-edit')export class RangeDateTimeEdit extends LitElement { protected override createRenderRoot() { return this; }
override render() { return html` <gui-range-date-time label="Server downtime" allow-edit .value=${downtimes} ></gui-range-date-time> `; }}<gui-range-date-time label="Server downtime" allow-edit value='[{"start":"2027-05-14T22:00:00","end":"2027-05-15T02:00:00"},{"start":"2027-06-11T23:00:00","end":"2027-06-12T01:00:00"}]'></gui-range-date-time>Disabled and read-only
Section titled “Disabled and read-only”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.
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> );}import { Component, CUSTOM_ELEMENTS_SCHEMA } from '@angular/core';import type { DateTimeRange } from '@golemui/gui-components';import '@golemui/gui-components/range-date-time-input';
@Component({ selector: 'app-range-date-time-disabled-readonly', schemas: [CUSTOM_ELEMENTS_SCHEMA], template: ` <div class="gui-grid"> <div class="gui-grid__cell"> <gui-range-date-time label="Frozen deploys" [value]="holidays" disabled ></gui-range-date-time> </div> <div class="gui-grid__cell"> <gui-range-date-time label="Booked slots" [value]="holidays" readonly ></gui-range-date-time> </div> </div> `,})export class RangeDateTimeDisabledReadonlyComponent { holidays: DateTimeRange[] = [ { start: '2027-12-20T18:00:00', end: '2028-01-02T09:00:00' }, ];}<script setup lang="ts">import type { DateTimeRange } from '@golemui/gui-components';import '@golemui/gui-components/range-date-time-input';import '@golemui/gui-components/index.css';
const value: DateTimeRange[] = [ { start: '2027-12-20T18:00:00', end: '2028-01-02T09:00:00' },];</script>
<template> <div class="gui-grid"> <div class="gui-grid__cell"> <gui-range-date-time label="Frozen deploys" :value.prop="value" :disabled="true" ></gui-range-date-time> </div> <div class="gui-grid__cell"> <gui-range-date-time label="Booked slots" :value.prop="value" :readOnly="true" ></gui-range-date-time> </div> </div></template>import { LitElement, html } from 'lit';import { customElement } from 'lit/decorators.js';import type { DateTimeRange } from '@golemui/gui-components';import '@golemui/gui-components/range-date-time-input';import '@golemui/gui-components/index.css';
const freeze: DateTimeRange[] = [ { start: '2027-12-20T18:00:00', end: '2028-01-02T09:00:00' },];
@customElement('range-date-time-disabled-readonly')export class RangeDateTimeDisabledReadonly extends LitElement { protected override createRenderRoot() { return this; }
override render() { return html` <div class="gui-grid"> <div class="gui-grid__cell"> <gui-range-date-time label="Frozen deploys" .value=${freeze} disabled ></gui-range-date-time> </div> <div class="gui-grid__cell"> <gui-range-date-time label="Booked slots" .value=${freeze} readonly ></gui-range-date-time> </div> </div> `; }}<div class="gui-grid"> <div class="gui-grid__cell"> <gui-range-date-time label="Frozen deploys" value='[{"start":"2027-12-20T18:00:00","end":"2028-01-02T09:00:00"}]' disabled ></gui-range-date-time> </div> <div class="gui-grid__cell"> <gui-range-date-time label="Booked slots" value='[{"start":"2027-12-20T18:00:00","end":"2028-01-02T09:00:00"}]' readonly ></gui-range-date-time> </div></div>In a form
Section titled “In a form”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.
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> );}import { Component, CUSTOM_ELEMENTS_SCHEMA, signal } from '@angular/core';import '@golemui/gui-components/button';import '@golemui/gui-components/range-date-time-input';
@Component({ selector: 'app-range-date-time-native-form', schemas: [CUSTOM_ELEMENTS_SCHEMA], template: ` <form id="outage-form" #form (submit)="onSubmit($event, form)"> <div class="gui-grid"> <div class="gui-grid__cell"> <gui-range-date-time label="Planned outage" name="outage" required ></gui-range-date-time> </div> <div class="gui-grid__cell"> <gui-button type="submit" label="Schedule"></gui-button> </div> </div> <p id="outage-result" role="status">{{ result() }}</p> </form> `,})export class RangeDateTimeNativeFormComponent { result = signal('');
onSubmit(event: Event, form: HTMLFormElement) { event.preventDefault(); const ranges = new FormData(form).getAll('outage'); this.result.set(`Submitted ${ranges.join(', ')}`); }}<script setup lang="ts">import { ref, useTemplateRef } from 'vue';import '@golemui/gui-components/button';import '@golemui/gui-components/range-date-time-input';import '@golemui/gui-components/index.css';
const form = useTemplateRef('form');const result = ref('');
function onSubmit() { if (!form.value) return; const ranges = new FormData(form.value).getAll('outage'); result.value = `Submitted ${ranges.join(', ')}`;}</script>
<template> <form id="outage-form" ref="form" @submit.prevent="onSubmit"> <div class="gui-grid"> <div class="gui-grid__cell"> <gui-range-date-time label="Planned outage" name="outage" :required="true" ></gui-range-date-time> </div> <div class="gui-grid__cell"> <gui-button type="submit" label="Schedule"></gui-button> </div> </div> <p id="outage-result" role="status">{{ result }}</p> </form></template>import { LitElement, html } from 'lit';import { customElement, query, state } from 'lit/decorators.js';import '@golemui/gui-components/button';import '@golemui/gui-components/range-date-time-input';import '@golemui/gui-components/index.css';
@customElement('range-date-time-native-form')export class RangeDateTimeNativeForm extends LitElement { @query('form') private form!: HTMLFormElement; @state() private ranges?: FormDataEntryValue[];
protected override createRenderRoot() { return this; }
private onSubmit(event: SubmitEvent) { event.preventDefault(); this.ranges = new FormData(this.form).getAll('outage'); }
override render() { return html` <form id="outage-form" @submit=${this.onSubmit}> <div class="gui-grid"> <div class="gui-grid__cell"> <gui-range-date-time label="Planned outage" name="outage" required ></gui-range-date-time> </div> <div class="gui-grid__cell"> <gui-button type="submit" label="Schedule"></gui-button> </div> </div> <p role="status"> ${this.ranges && `Submitted ${this.ranges.join(', ')}`} </p> </form> `; }}<form id="outage-form"> <div class="gui-grid"> <div class="gui-grid__cell"> <gui-range-date-time label="Planned outage" name="outage" required></gui-range-date-time> </div> <div class="gui-grid__cell"> <gui-button type="submit" label="Schedule"></gui-button> </div> </div> <p id="outage-result" role="status"></p></form>
<script type="module"> const form = document.querySelector('#outage-form');
form.addEventListener('submit', (event) => { event.preventDefault(); const ranges = new FormData(form).getAll('outage'); document.querySelector('#outage-result').textContent = `Submitted ${ranges.join(', ')}`; });</script>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.
Styling
Section titled “Styling”Anatomy
Section titled “Anatomy”<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 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>| Class | Part |
|---|---|
gui-field | The element itself. Lines its parts up with the fields next to it in a gui-grid row. |
gui-widget-input, gui-parts-ring | The part that looks like a field. gui-parts-ring draws its focus ring and its error border. |
gui-range-date-time-input | The field of this element. gui-range-date-time-input--icon is on it when there is an icon. |
gui-range-date-time-input__pills | The pills, a <gui-pills> element. gui-pills__pill is one range. |
gui-range-date-time-input__field | The start, or the end: a gui-parts group of parts. |
gui-range-date-time-input__part | One part. gui-range-date-time-input__year is the year and __dayperiod the AM/PM button. |
gui-range-date-time-input__time-cluster | The hour and minute, which keep their left-to-right order in a right-to-left page. |
gui-range-date-time-input__separator | The text between the parts, and between the start and the end (separator, - by default). |
gui-range-date-time-input__visual-underline | The 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.
Tokens
Section titled “Tokens”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.
/* 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.
Accessibility
Section titled “Accessibility”- Name. The
labelnames 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-labelandend-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-labelandday-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
hintis the field’s description. While errors show, the field hasaria-invalid="true"and points at them witharia-errormessage, and screen readers announce new errors as they appear. - Required.
requiredsetsaria-requiredon every part; the visual marker is hidden from screen readers.
In the parts:
| Key | Action |
|---|---|
| Tab | Moves focus to the first part of the start, and away from the field. The other parts are not tab stops. |
| Digits | Fill 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 Delete | Clear the part. On an empty part, while every part is empty, they move to the last pill. |
| Enter | Adds 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.