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.
import { GuiRangeDateTimePicker } from '@golemui/gui-components/react';import '@golemui/gui-components/index.css';
export function RangeDateTimePickerBasic() { return <GuiRangeDateTimePicker label="Conference sessions" minuteStep={30} />;}import { Component, CUSTOM_ELEMENTS_SCHEMA } from '@angular/core';import '@golemui/gui-components/range-date-time-picker';
@Component({ selector: 'app-range-date-time-picker-basic', schemas: [CUSTOM_ELEMENTS_SCHEMA], template: ` <gui-range-date-time-picker label="Conference sessions" [minuteStep]="30" ></gui-range-date-time-picker> `,})export class RangeDateTimePickerBasicComponent {}<script setup lang="ts">import '@golemui/gui-components/range-date-time-picker';import '@golemui/gui-components/index.css';</script>
<template> <gui-range-date-time-picker label="Conference sessions" :minute-step="30" ></gui-range-date-time-picker></template>import { LitElement, html } from 'lit';import { customElement } from 'lit/decorators.js';import '@golemui/gui-components/range-date-time-picker';import '@golemui/gui-components/index.css';
@customElement('range-date-time-picker-basic')export class RangeDateTimePickerBasic extends LitElement { protected override createRenderRoot() { return this; }
override render() { return html` <gui-range-date-time-picker label="Conference sessions" minute-step="30" ></gui-range-date-time-picker> `; }}<gui-range-date-time-picker label="Conference sessions" minute-step="30"></gui-range-date-time-picker>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.
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 ?? [])} /> );}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, GuiRangeDateTimePickerEvents } from '@golemui/gui-components';import '@golemui/gui-components/range-date-time-picker';
type ChangeEvent = GuiEventMap<typeof GuiRangeDateTimePickerEvents>['gui-change'];
@Component({ selector: 'app-sessions-field', schemas: [CUSTOM_ELEMENTS_SCHEMA], template: ` <gui-range-date-time-picker label="Conference sessions" name="sessions" [value]="sessions" (gui-change)="onChange($event)" ></gui-range-date-time-picker> `,})export class SessionsFieldComponent { sessions = [];
onChange(event: Event) { this.sessions = (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-picker';import '@golemui/gui-components/index.css';
const sessions = ref([]);</script>
<template> <gui-range-date-time-picker label="Conference sessions" name="sessions" :value="sessions" @gui-change="sessions = $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-picker';import '@golemui/gui-components/index.css';
@customElement('sessions-field')export class SessionsField extends LitElement { @state() private sessions = [];
protected override createRenderRoot() { return this; }
override render() { return html` <gui-range-date-time-picker label="Conference sessions" name="sessions" .value=${this.sessions} @gui-change=${(event: CustomEvent) => (this.sessions = event.detail.value)} ></gui-range-date-time-picker> `; }}With any bundler, such as Vite. Import the element once, anywhere, and use the tag in your markup.
<gui-range-date-time-picker label="Conference sessions" name="sessions"></gui-range-date-time-picker>
<script type="module" src="./main.js"></script>import '@golemui/gui-components/range-date-time-picker';import '@golemui/gui-components/index.css';
const element = document.querySelector('gui-range-date-time-picker');
element.addEventListener('gui-change', (event) => { console.log(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. |
Examples
Section titled “Examples”Picking a range
Section titled “Picking a range”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:
- 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.
- 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-stepsets 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.
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.
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.
Bounds and disabled times
Section titled “Bounds and disabled times”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.
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([]); }} /> );}import { Component, CUSTOM_ELEMENTS_SCHEMA, signal } from '@angular/core';import type { DateTimeRange, GuiEventMap, GuiRangeDateTimePickerEvents,} from '@golemui/gui-components';import '@golemui/gui-components/range-date-time-picker';
type RangeDateTimePickerEvent< K extends keyof typeof GuiRangeDateTimePickerEvents,> = GuiEventMap<typeof GuiRangeDateTimePickerEvents>[K];
@Component({ selector: 'app-range-date-time-picker-bounds', schemas: [CUSTOM_ELEMENTS_SCHEMA], template: ` <gui-range-date-time-picker 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" min-date-time="2027-03-01T08:00:00" max-date-time="2027-03-31T22:00:00" [disabledRanges]="closed" [value]="booking" [errors]="errors()" (gui-input-error)="onInputError($event)" (gui-input)="errors.set([])" ></gui-range-date-time-picker> `,})export class RangeDateTimePickerBoundsComponent { 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' }, ];
booking: DateTimeRange[] = [ { start: '2027-03-03T09:00:00', end: '2027-03-04T17:00:00' }, ];
errors = signal<string[]>([]);
onInputError(event: Event) { this.errors.set([ (event as RangeDateTimePickerEvent<'gui-input-error'>).detail.message, ]); }}<script setup lang="ts">import { ref } from 'vue';import type { DateTimeRange } from '@golemui/gui-components';import '@golemui/gui-components/range-date-time-picker';import '@golemui/gui-components/index.css';
const disabledRanges: 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' },];const value: DateTimeRange[] = [ { start: '2027-03-03T09:00:00', end: '2027-03-04T17:00:00' },];const errors = ref<string[]>([]);</script>
<template> <gui-range-date-time-picker 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." :minute-step="30" min-date-time="2027-03-01T08:00:00" max-date-time="2027-03-31T22:00:00" :disabledRanges.prop="disabledRanges" :value.prop="value" :errors.prop="errors" @gui-input-error="errors = [$event.detail.message]" @gui-input="errors = []" ></gui-range-date-time-picker></template>import { LitElement, html } from 'lit';import { customElement, state } from 'lit/decorators.js';import type { DateTimeRange, GuiEventMap, GuiRangeDateTimePickerEvents,} from '@golemui/gui-components';import '@golemui/gui-components/range-date-time-picker';import '@golemui/gui-components/index.css';
type RangeDateTimePickerEvent< K extends keyof typeof GuiRangeDateTimePickerEvents,> = GuiEventMap<typeof GuiRangeDateTimePickerEvents>[K];
const closures: 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' },];
const booking: DateTimeRange[] = [ { start: '2027-03-03T09:00:00', end: '2027-03-04T17:00:00' },];
@customElement('range-date-time-picker-bounds')export class RangeDateTimePickerBounds extends LitElement { @state() private errors: string[] = [];
protected override createRenderRoot() { return this; }
private onInputError(event: RangeDateTimePickerEvent<'gui-input-error'>) { this.errors = [event.detail.message]; }
private onInput() { this.errors = []; }
override render() { return html` <gui-range-date-time-picker 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." minute-step="30" min-date-time="2027-03-01T08:00:00" max-date-time="2027-03-31T22:00:00" .disabledRanges=${closures} .value=${booking} .errors=${this.errors} @gui-input-error=${this.onInputError} @gui-input=${this.onInput} ></gui-range-date-time-picker> `; }}<gui-range-date-time-picker 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." minute-step="30" min-date-time="2027-03-01T08:00:00" max-date-time="2027-03-31T22:00:00" disabled-ranges='[{"start":"2027-03-10T00:00:00","end":"2027-03-10T23:59:59"},{"start":"2027-03-15T12:00:00","end":"2027-03-15T14:00:00"}]' value='[{"start":"2027-03-03T09:00:00","end":"2027-03-04T17:00:00"}]'></gui-range-date-time-picker>
<script type="module"> const venue = document.querySelector('#venue-booking');
venue.addEventListener('gui-input-error', (event) => { venue.errors = [event.detail.message]; }); venue.addEventListener('gui-input', () => { venue.errors = []; });</script>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.
Calendar options
Section titled “Calendar options”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.
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
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.
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} /> );}import { Component, CUSTOM_ELEMENTS_SCHEMA } from '@angular/core';import type { DateTimeRange } from '@golemui/gui-components';import '@golemui/gui-components/range-date-time-picker';
@Component({ selector: 'app-range-date-time-picker-edit', schemas: [CUSTOM_ELEMENTS_SCHEMA], template: ` <gui-range-date-time-picker label="Conference sessions" [minuteStep]="30" allow-edit [value]="sessions" ></gui-range-date-time-picker> `,})export class RangeDateTimePickerEditComponent { 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' }, ];}<script setup lang="ts">import type { DateTimeRange } from '@golemui/gui-components';import '@golemui/gui-components/range-date-time-picker';import '@golemui/gui-components/index.css';
const value: 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' },];</script>
<template> <gui-range-date-time-picker label="Conference sessions" :minute-step="30" :allow-edit="true" :value.prop="value" ></gui-range-date-time-picker></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-picker';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' },];
@customElement('range-date-time-picker-edit')export class RangeDateTimePickerEdit extends LitElement { protected override createRenderRoot() { return this; }
override render() { return html` <gui-range-date-time-picker label="Conference sessions" minute-step="30" allow-edit .value=${sessions} ></gui-range-date-time-picker> `; }}<gui-range-date-time-picker label="Conference sessions" minute-step="30" allow-edit value='[{"start":"2027-03-03T09:00:00","end":"2027-03-03T12:30:00"},{"start":"2027-03-04T14:00:00","end":"2027-03-04T17:00:00"}]'></gui-range-date-time-picker>In a form
Section titled “In a form”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.
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> );}import { Component, CUSTOM_ELEMENTS_SCHEMA, signal } from '@angular/core';import '@golemui/gui-components/button';import '@golemui/gui-components/range-date-time-picker';
@Component({ selector: 'app-range-date-time-picker-native-form', schemas: [CUSTOM_ELEMENTS_SCHEMA], template: ` <form id="charter-form" #form (submit)="onSubmit($event, form)"> <div class="gui-grid"> <div class="gui-grid__cell"> <gui-range-date-time-picker label="Boat charter" name="charter" [minuteStep]="60" required ></gui-range-date-time-picker> </div> <div class="gui-grid__cell"> <gui-button type="submit" label="Reserve"></gui-button> </div> </div> <p id="charter-result" role="status">{{ result() }}</p> </form> `,})export class RangeDateTimePickerNativeFormComponent { result = signal('');
onSubmit(event: Event, form: HTMLFormElement) { event.preventDefault(); const ranges = new FormData(form).getAll('charter'); 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-picker';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('charter'); result.value = `Submitted ${ranges.join(', ')}`;}</script>
<template> <form id="charter-form" ref="form" @submit.prevent="onSubmit"> <div class="gui-grid"> <div class="gui-grid__cell"> <gui-range-date-time-picker label="Boat charter" name="charter" :minute-step="60" :required="true" ></gui-range-date-time-picker> </div> <div class="gui-grid__cell"> <gui-button type="submit" label="Reserve"></gui-button> </div> </div> <p id="charter-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-picker';import '@golemui/gui-components/index.css';
@customElement('range-date-time-picker-native-form')export class RangeDateTimePickerNativeForm 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('charter'); }
override render() { return html` <form id="charter-form" @submit=${this.onSubmit}> <div class="gui-grid"> <div class="gui-grid__cell"> <gui-range-date-time-picker label="Boat charter" name="charter" minute-step="60" required ></gui-range-date-time-picker> </div> <div class="gui-grid__cell"> <gui-button type="submit" label="Reserve"></gui-button> </div> </div> <p role="status"> ${this.ranges && `Submitted ${this.ranges.join(', ')}`} </p> </form> `; }}<form id="charter-form"> <div class="gui-grid"> <div class="gui-grid__cell"> <gui-range-date-time-picker label="Boat charter" name="charter" minute-step="60" required ></gui-range-date-time-picker> </div> <div class="gui-grid__cell"> <gui-button type="submit" label="Reserve"></gui-button> </div> </div> <p id="charter-result" role="status"></p></form>
<script type="module"> const form = document.querySelector('#charter-form');
form.addEventListener('submit', (event) => { event.preventDefault(); const ranges = new FormData(form).getAll('charter'); document.querySelector('#charter-result').textContent = `Submitted ${ranges.join(', ')}`; });</script>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.
Styling
Section titled “Styling”Anatomy
Section titled “Anatomy”<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> <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>| Class | Part |
|---|---|
gui-label, gui-label__text | The label, and its text with the required marker. |
gui-widget | The box around the field, the button and the panel. |
gui-range-date-time-picker__arrow | The button that opens and closes the popup. Its gui-caret turns while the popup is open. |
gui-picker__panel | The popup. While there are errors, it repeats them at its bottom. |
gui-validator | The 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.
Tokens
Section titled “Tokens”The field reads the tokens of the Date range field, and the calendar those of the Date-time range calendar. The popup reads these:
| Token | What it styles |
|---|---|
--gui-bg-default | The background of the popup. |
--gui-border-default | The border of the popup. |
--gui-radius-md | The corner radius of the popup. |
--gui-shadow-md | The shadow of the popup. |
--gui-space-2 | The padding of the popup. |
--gui-intent-error | The popup’s border while invalid. |
--gui-text-default | The toggle button’s caret. |
/* 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.
Accessibility
Section titled “Accessibility”- Name. The
labelnames 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 hasaria-haspopup="dialog", andaria-expandedtells 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-labelanddisabled-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.
| Key | Action |
|---|---|
| Tab | Moves 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 part | Opens the popup, and keeps the focus in the field so the user can type. |
| Enter Space | On 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 calendar | Move 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 calendar | Move to the first or last day of the week the user can pick, within the month. |
| Enter Space | In the calendar, picks the focused day: the first pick starts the span, the second completes it. |
| Escape | Closes 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.