Time range picker
<gui-range-time-picker> is a time range field with two lists in a popup, one for the start time
and one for the end. Once the user picks both, the range becomes a pill. The field holds as many
ranges as the user adds.
import { GuiRangeTimePicker } from '@golemui/gui-components/react';import '@golemui/gui-components/index.css';
export function RangeTimePickerBasic() { return <GuiRangeTimePicker label="Meeting time" minuteStep={30} />;}import { Component, CUSTOM_ELEMENTS_SCHEMA } from '@angular/core';import '@golemui/gui-components/range-time-picker';
@Component({ selector: 'app-range-time-picker-basic', schemas: [CUSTOM_ELEMENTS_SCHEMA], template: ` <gui-range-time-picker label="Meeting time" [minuteStep]="30" ></gui-range-time-picker> `,})export class RangeTimePickerBasicComponent {}<script setup lang="ts">import '@golemui/gui-components/range-time-picker';import '@golemui/gui-components/index.css';</script>
<template> <gui-range-time-picker label="Meeting time" :minute-step="30" ></gui-range-time-picker></template>import { LitElement, html } from 'lit';import { customElement } from 'lit/decorators.js';import '@golemui/gui-components/range-time-picker';import '@golemui/gui-components/index.css';
@customElement('range-time-picker-basic')export class RangeTimePickerBasic extends LitElement { protected override createRenderRoot() { return this; }
override render() { return html` <gui-range-time-picker label="Meeting time" minute-step="30" ></gui-range-time-picker> `; }}<gui-range-time-picker label="Meeting time" minute-step="30"></gui-range-time-picker>It combines a Time range field with two time lists. For a single time, use the Time picker.
The element’s entry point is @golemui/gui-components/range-time-picker, which registers
<gui-range-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 { GuiRangeTimePicker } from '@golemui/gui-components/react';import '@golemui/gui-components/index.css';
export function MeetingField() { const [meeting, setMeeting] = useState([]);
return ( <GuiRangeTimePicker label="Meeting time" name="meeting" value={meeting} onGuiChange={(event) => setMeeting(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, GuiRangeTimePickerEvents } from '@golemui/gui-components';import '@golemui/gui-components/range-time-picker';
type ChangeEvent = GuiEventMap<typeof GuiRangeTimePickerEvents>['gui-change'];
@Component({ selector: 'app-meeting-field', schemas: [CUSTOM_ELEMENTS_SCHEMA], template: ` <gui-range-time-picker label="Meeting time" name="meeting" [value]="meeting" (gui-change)="onChange($event)" ></gui-range-time-picker> `,})export class MeetingFieldComponent { meeting = [];
onChange(event: Event) { this.meeting = (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-time-picker';import '@golemui/gui-components/index.css';
const meeting = ref([]);</script>
<template> <gui-range-time-picker label="Meeting time" name="meeting" :value="meeting" @gui-change="meeting = $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-time-picker';import '@golemui/gui-components/index.css';
@customElement('meeting-field')export class MeetingField extends LitElement { @state() private meeting = [];
protected override createRenderRoot() { return this; }
override render() { return html` <gui-range-time-picker label="Meeting time" name="meeting" .value=${this.meeting} @gui-change=${(event: CustomEvent) => (this.meeting = event.detail.value)} ></gui-range-time-picker> `; }}With any bundler, such as Vite. Import the element once, anywhere, and use the tag in your markup.
<gui-range-time-picker label="Meeting time" name="meeting"></gui-range-time-picker>
<script type="module" src="./main.js"></script>import '@golemui/gui-components/range-time-picker';import '@golemui/gui-components/index.css';
const element = document.querySelector('gui-range-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. |
toggleAriaLabel
attribute toggle-aria-label | string | – | Accessible name of the button that opens the popup. 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. |
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 | TimeRange[] | [] | The time ranges, as { start, end } ISO times. |
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. |
startTimeAriaLabel
attribute start-time-aria-label | string | – | Accessible name of the start time field. An empty value removes it. |
endTimeAriaLabel
attribute end-time-aria-label | string | – | Accessible name of the end time field. An empty value removes it. |
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. |
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. |
minTime
attribute min-time | string | – | Earliest selectable time, as an ISO time (HH:mm:ss). |
maxTime
attribute max-time | string | – | Latest selectable time, as an ISO time (HH:mm:ss). |
disabledRanges
attribute disabled-ranges | TimeRange[] | – | Times that cannot be picked, as { start, end } ISO time ranges. |
allowCustomTime
attribute allow-custom-time | boolean | false | Allows typing any time, not only picking one from the list. |
height | number | – | Height of each time list, in pixels. |
itemHeight
attribute item-height | number | – | Height of each time in the lists, in pixels. |
minTimeMessage
attribute min-time-message | string | – | Error for a time before minTime. |
maxTimeMessage
attribute max-time-message | string | – | Error for a time after maxTime. |
rangeOrderMessage
attribute range-order-message | string | – | Error when the end time is not after the start time. |
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. |
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-time-grid-height | – | Height of the time grid. |
--gui-calendar-time-button-height | – | Height of each time in the grid. |
--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. The user picks a start time in the first list and an end time in the second, in either order. The end list only offers times after the start. With both picked, the range becomes a pill.
By default the user picks times from the lists only: the parts of the field don’t take typing, but ↑ and ↓ on a part step its time through the list, and Enter adds the range.
Steps and labels
Section titled “Steps and labels”minute-step sets the minutes between the times in the lists. start-time-label and
end-time-label change the headings of the two lists, which also name them for screen readers.
import { GuiRangeTimePicker } from '@golemui/gui-components/react';import '@golemui/gui-components/index.css';
export function RangeTimePickerStep() { return ( <GuiRangeTimePicker label="Consultation slots" minuteStep={15} startTimeLabel="From" endTimeLabel="To" /> );}import { Component, CUSTOM_ELEMENTS_SCHEMA } from '@angular/core';import '@golemui/gui-components/range-time-picker';
@Component({ selector: 'app-range-time-picker-step', schemas: [CUSTOM_ELEMENTS_SCHEMA], template: ` <gui-range-time-picker label="Consultation slots" [minuteStep]="15" start-time-label="From" end-time-label="To" ></gui-range-time-picker> `,})export class RangeTimePickerStepComponent {}<script setup lang="ts">import '@golemui/gui-components/range-time-picker';import '@golemui/gui-components/index.css';</script>
<template> <gui-range-time-picker label="Consultation slots" :minute-step="15" start-time-label="From" end-time-label="To" ></gui-range-time-picker></template>import { LitElement, html } from 'lit';import { customElement } from 'lit/decorators.js';import '@golemui/gui-components/range-time-picker';import '@golemui/gui-components/index.css';
@customElement('range-time-picker-step')export class RangeTimePickerStep extends LitElement { protected override createRenderRoot() { return this; }
override render() { return html` <gui-range-time-picker label="Consultation slots" minute-step="15" start-time-label="From" end-time-label="To" ></gui-range-time-picker> `; }}<gui-range-time-picker label="Consultation slots" minute-step="15" start-time-label="From" end-time-label="To"></gui-range-time-picker>hour-format sets a 12 or 24-hour clock; without it the picker follows locale-id. height
and item-height set the height of the lists and of each time in them, in pixels.
The value
Section titled “The value”value is a list of ranges. Each range is an object with a start and an end, both ISO times
(HH:mm:ss), and the end always comes after the start. As an attribute, value is a JSON array.
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-time and max-time limit the times in the lists, as ISO times. disabled-ranges takes a
JSON list of { start, end } time ranges: their times show as disabled in the lists, and a range
that overlaps one, even only at an end, is rejected.
import { useState } from 'react';import { GuiRangeTimePicker } from '@golemui/gui-components/react';import type { TimeRange } from '@golemui/gui-components';import '@golemui/gui-components/index.css';
const lunchBreak: TimeRange[] = [{ start: '12:00:00', end: '13:00:00' }];
export function RangeTimePickerBounds() { const [errors, setErrors] = useState<string[]>([]);
return ( <GuiRangeTimePicker id="class-times" label="Class times" hint="Classes run from 8:00 AM to 6:00 PM, with a lunch break from 12:00 PM to 1:00 PM." minuteStep={30} minTime="08:00:00" maxTime="18:00:00" disabledRanges={lunchBreak} errors={errors} onGuiInputError={(event) => setErrors([event.detail.message])} onGuiInput={() => setErrors([])} /> );}import { Component, CUSTOM_ELEMENTS_SCHEMA, signal } from '@angular/core';import type { GuiEventMap, GuiRangeTimePickerEvents, TimeRange,} from '@golemui/gui-components';import '@golemui/gui-components/range-time-picker';
type RangeTimePickerEvent<K extends keyof typeof GuiRangeTimePickerEvents> = GuiEventMap<typeof GuiRangeTimePickerEvents>[K];
@Component({ selector: 'app-range-time-picker-bounds', schemas: [CUSTOM_ELEMENTS_SCHEMA], template: ` <gui-range-time-picker id="class-times" label="Class times" hint="Classes run from 8:00 AM to 6:00 PM, with a lunch break from 12:00 PM to 1:00 PM." [minuteStep]="30" min-time="08:00:00" max-time="18:00:00" [disabledRanges]="lunch" [errors]="errors()" (gui-input-error)="onInputError($event)" (gui-input)="errors.set([])" ></gui-range-time-picker> `,})export class RangeTimePickerBoundsComponent { lunch: TimeRange[] = [{ start: '12:00:00', end: '13:00:00' }];
errors = signal<string[]>([]);
onInputError(event: Event) { this.errors.set([ (event as RangeTimePickerEvent<'gui-input-error'>).detail.message, ]); }}<script setup lang="ts">import { ref } from 'vue';import type { TimeRange } from '@golemui/gui-components';import '@golemui/gui-components/range-time-picker';import '@golemui/gui-components/index.css';
const disabledRanges: TimeRange[] = [{ start: '12:00:00', end: '13:00:00' }];const errors = ref<string[]>([]);</script>
<template> <gui-range-time-picker id="class-times" label="Class times" hint="Classes run from 8:00 AM to 6:00 PM, with a lunch break from 12:00 PM to 1:00 PM." :minute-step="30" min-time="08:00:00" max-time="18:00:00" :disabledRanges.prop="disabledRanges" :errors.prop="errors" @gui-input-error="errors = [$event.detail.message]" @gui-input="errors = []" ></gui-range-time-picker></template>import { LitElement, html } from 'lit';import { customElement, state } from 'lit/decorators.js';import type { GuiEventMap, GuiRangeTimePickerEvents, TimeRange,} from '@golemui/gui-components';import '@golemui/gui-components/range-time-picker';import '@golemui/gui-components/index.css';
type RangeTimePickerEvent<K extends keyof typeof GuiRangeTimePickerEvents> = GuiEventMap<typeof GuiRangeTimePickerEvents>[K];
const lunch: TimeRange[] = [{ start: '12:00:00', end: '13:00:00' }];
@customElement('range-time-picker-bounds')export class RangeTimePickerBounds extends LitElement { @state() private errors: string[] = [];
protected override createRenderRoot() { return this; }
private onInputError(event: RangeTimePickerEvent<'gui-input-error'>) { this.errors = [event.detail.message]; }
private onInput() { this.errors = []; }
override render() { return html` <gui-range-time-picker id="class-times" label="Class times" hint="Classes run from 8:00 AM to 6:00 PM, with a lunch break from 12:00 PM to 1:00 PM." minute-step="30" min-time="08:00:00" max-time="18:00:00" .disabledRanges=${lunch} .errors=${this.errors} @gui-input-error=${this.onInputError} @gui-input=${this.onInput} ></gui-range-time-picker> `; }}<gui-range-time-picker id="class-times" label="Class times" hint="Classes run from 8:00 AM to 6:00 PM, with a lunch break from 12:00 PM to 1:00 PM." minute-step="30" min-time="08:00:00" max-time="18:00:00" disabled-ranges='[{"start":"12:00:00","end":"13:00:00"}]'></gui-range-time-picker>
<script type="module"> const classes = document.querySelector('#class-times');
classes.addEventListener('gui-input-error', (event) => { classes.errors = [event.detail.message]; }); classes.addEventListener('gui-input', () => { classes.errors = []; });</script>Pick 9:00 AM and then 6:00 PM above: the range spans the lunch break, so the popup closes, the field
keeps both times for the user to correct, and the picker 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
min-time-message, max-time-message, range-order-message, disabled-range-message and
no-available-times-message, the text of a list with no time to offer.
Typed times
Section titled “Typed times”allow-custom-time lets the user type any time in the parts, not only the times of the lists.
import { GuiRangeTimePicker } from '@golemui/gui-components/react';import '@golemui/gui-components/index.css';
export function RangeTimePickerCustomTime() { return ( <GuiRangeTimePicker label="Quiet hours" minuteStep={60} hourFormat="24" allowCustomTime /> );}import { Component, CUSTOM_ELEMENTS_SCHEMA } from '@angular/core';import '@golemui/gui-components/range-time-picker';
@Component({ selector: 'app-range-time-picker-custom-time', schemas: [CUSTOM_ELEMENTS_SCHEMA], template: ` <gui-range-time-picker label="Quiet hours" [minuteStep]="60" hour-format="24" allow-custom-time ></gui-range-time-picker> `,})export class RangeTimePickerCustomTimeComponent {}<script setup lang="ts">import '@golemui/gui-components/range-time-picker';import '@golemui/gui-components/index.css';</script>
<template> <gui-range-time-picker label="Quiet hours" :minute-step="60" hour-format="24" :allow-custom-time="true" ></gui-range-time-picker></template>import { LitElement, html } from 'lit';import { customElement } from 'lit/decorators.js';import '@golemui/gui-components/range-time-picker';import '@golemui/gui-components/index.css';
@customElement('range-time-picker-custom-time')export class RangeTimePickerCustomTime extends LitElement { protected override createRenderRoot() { return this; }
override render() { return html` <gui-range-time-picker label="Quiet hours" minute-step="60" hour-format="24" allow-custom-time ></gui-range-time-picker> `; }}<gui-range-time-picker label="Quiet hours" minute-step="60" hour-format="24" allow-custom-time></gui-range-time-picker>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 { GuiRangeTimePicker } from '@golemui/gui-components/react';import type { TimeRange } from '@golemui/gui-components';import '@golemui/gui-components/index.css';
const meetings: TimeRange[] = [ { start: '09:00:00', end: '10:30:00' }, { start: '15:00:00', end: '16:00:00' },];
export function RangeTimePickerEdit() { return ( <GuiRangeTimePicker label="Meeting time" minuteStep={30} allowEdit value={meetings} /> );}import { Component, CUSTOM_ELEMENTS_SCHEMA } from '@angular/core';import type { TimeRange } from '@golemui/gui-components';import '@golemui/gui-components/range-time-picker';
@Component({ selector: 'app-range-time-picker-edit', schemas: [CUSTOM_ELEMENTS_SCHEMA], template: ` <gui-range-time-picker label="Meeting time" [minuteStep]="30" allow-edit [value]="meetings" ></gui-range-time-picker> `,})export class RangeTimePickerEditComponent { meetings: TimeRange[] = [ { start: '09:00:00', end: '10:30:00' }, { start: '15:00:00', end: '16:00:00' }, ];}<script setup lang="ts">import type { TimeRange } from '@golemui/gui-components';import '@golemui/gui-components/range-time-picker';import '@golemui/gui-components/index.css';
const value: TimeRange[] = [ { start: '09:00:00', end: '10:30:00' }, { start: '15:00:00', end: '16:00:00' },];</script>
<template> <gui-range-time-picker label="Meeting time" :minute-step="30" :allow-edit="true" :value.prop="value" ></gui-range-time-picker></template>import { LitElement, html } from 'lit';import { customElement } from 'lit/decorators.js';import type { TimeRange } from '@golemui/gui-components';import '@golemui/gui-components/range-time-picker';import '@golemui/gui-components/index.css';
const meetings: TimeRange[] = [ { start: '09:00:00', end: '10:30:00' }, { start: '15:00:00', end: '16:00:00' },];
@customElement('range-time-picker-edit')export class RangeTimePickerEdit extends LitElement { protected override createRenderRoot() { return this; }
override render() { return html` <gui-range-time-picker label="Meeting time" minute-step="30" allow-edit .value=${meetings} ></gui-range-time-picker> `; }}<gui-range-time-picker label="Meeting time" minute-step="30" allow-edit value='[{"start":"09:00:00","end":"10:30:00"},{"start":"15:00:00","end":"16:00:00"}]'></gui-range-time-picker>In a form
Section titled “In a form”With a name, <gui-range-time-picker> submits one entry per range under that name. Each entry
is the range as JSON, such as {"start":"09:00:00","end":"10:30:00"}. Read them all with
FormData.getAll().
The picker blocks the submit while the field has no range and required is set, and also when a
range of its value is outside min-time and max-time or overlaps disabled-ranges, even one you
set from code. The browser shows the same message as the field.
import { useState } from 'react';import { GuiButton, GuiRangeTimePicker } from '@golemui/gui-components/react';import '@golemui/gui-components/index.css';
export function RangeTimePickerNativeForm() { const [result, setResult] = useState('');
return ( <form id="availability-form" onSubmit={(event) => { event.preventDefault(); const ranges = new FormData(event.currentTarget).getAll('availability'); setResult(`Submitted ${ranges.join(', ')}`); }} > <div className="gui-grid"> <div className="gui-grid__cell"> <GuiRangeTimePicker label="Availability" name="availability" minuteStep={30} minTime="09:00:00" maxTime="17:00:00" required /> </div> <div className="gui-grid__cell"> <GuiButton type="submit" label="Save" /> </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-time-picker';
@Component({ selector: 'app-range-time-picker-native-form', schemas: [CUSTOM_ELEMENTS_SCHEMA], template: ` <form id="availability-form" #form (submit)="onSubmit($event, form)"> <div class="gui-grid"> <div class="gui-grid__cell"> <gui-range-time-picker label="Availability" name="availability" [minuteStep]="30" min-time="09:00:00" max-time="17:00:00" required ></gui-range-time-picker> </div> <div class="gui-grid__cell"> <gui-button type="submit" label="Save"></gui-button> </div> </div> <p id="availability-result" role="status">{{ result() }}</p> </form> `,})export class RangeTimePickerNativeFormComponent { result = signal('');
onSubmit(event: Event, form: HTMLFormElement) { event.preventDefault(); const ranges = new FormData(form).getAll('availability'); 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-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('availability'); result.value = `Submitted ${ranges.join(', ')}`;}</script>
<template> <form id="availability-form" ref="form" @submit.prevent="onSubmit"> <div class="gui-grid"> <div class="gui-grid__cell"> <gui-range-time-picker label="Availability" name="availability" :minute-step="30" min-time="09:00:00" max-time="17:00:00" :required="true" ></gui-range-time-picker> </div> <div class="gui-grid__cell"> <gui-button type="submit" label="Save"></gui-button> </div> </div> <p id="availability-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-time-picker';import '@golemui/gui-components/index.css';
@customElement('range-time-picker-native-form')export class RangeTimePickerNativeForm 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('availability'); }
override render() { return html` <form id="availability-form" @submit=${this.onSubmit}> <div class="gui-grid"> <div class="gui-grid__cell"> <gui-range-time-picker label="Availability" name="availability" minute-step="30" min-time="09:00:00" max-time="17:00:00" required ></gui-range-time-picker> </div> <div class="gui-grid__cell"> <gui-button type="submit" label="Save"></gui-button> </div> </div> <p role="status"> ${this.ranges && `Submitted ${this.ranges.join(', ')}`} </p> </form> `; }}<form id="availability-form"> <div class="gui-grid"> <div class="gui-grid__cell"> <gui-range-time-picker label="Availability" name="availability" minute-step="30" min-time="09:00:00" max-time="17:00:00" required ></gui-range-time-picker> </div> <div class="gui-grid__cell"> <gui-button type="submit" label="Save"></gui-button> </div> </div> <p id="availability-result" role="status"></p></form>
<script type="module"> const form = document.querySelector('#availability-form');
form.addEventListener('submit', (event) => { event.preventDefault(); const ranges = new FormData(form).getAll('availability'); document.querySelector('#availability-result').textContent = `Submitted ${ranges.join(', ')}`; });</script>How the elements submit, validate and reset is in Native forms.
Styling
Section titled “Styling”Anatomy
Section titled “Anatomy”<gui-range-time-picker> renders into the light DOM, so you can style every part with plain CSS.
It renders a <gui-range-time> field, a toggle button and, while open, a panel with two time
lists:
<gui-range-time-picker> <span class="gui-label" id="meeting_label"> <span class="gui-label__text">Meeting time</span> </span> <div class="gui-widget"> <gui-range-time id="meeting_time" class="gui-field"> <!-- The field: see the anatomy on the Time range page --> </gui-range-time> <button type="button" class="gui-range-time-picker__arrow" aria-haspopup="dialog" aria-label="Show time list" aria-expanded="true" aria-controls="meeting_popup" > <span class="gui-caret" aria-hidden="true"></span> </button> <div class="gui-picker__panel gui-range-time-picker__panel" role="dialog" id="meeting_popup" aria-labelledby="meeting_label" > <div class="gui-range-time-picker__columns"> <div class="gui-range-time-picker__column"> <span class="gui-range-time-picker__column-label">Start time</span> <gui-time-list class="gui-range-time-picker__list"> <div class="gui-time-list__viewport" role="listbox" aria-label="Start time" > <button type="button" role="option" class="gui-time-list__option" tabindex="0" aria-selected="false" aria-disabled="false" > 12:00 AM </button> <!-- The other times --> </div> </gui-time-list> </div> <div class="gui-range-time-picker__column"> <span class="gui-range-time-picker__column-label">End time</span> <!-- The end list --> </div> </div> </div> </div> <div class="gui-validator gui-validator--empty" id="meeting_errors" role="alert" ></div></gui-range-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-time-picker__arrow | The button that opens and closes the panel. Its gui-caret turns while the panel is open. |
gui-picker__panel, gui-range-time-picker__panel | The popup. While there are errors, it repeats them at its bottom. |
gui-range-time-picker__columns | The two lists, side by side. |
gui-range-time-picker__column | One list with its heading, gui-range-time-picker__column-label. |
gui-range-time-picker__list | A list, a <gui-time-list> element. gui-time-list__option is one time, with gui-time-list__option--selected on the picked one. |
gui-validator | The errors container. gui-validator--empty is on it while there are no errors. |
The field inside is a <gui-range-time>, whose
classes style the picker too. The lists are documented with the
Time picker. 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. The panel and its lists read these:
| Token | What it styles |
|---|---|
--gui-bg-default | The background of the panel and the lists. |
--gui-border-default | The border of the panel and the lists. |
--gui-radius-md | The corner radius of the panel and the lists. |
--gui-shadow-md | The shadow of the panel. |
--gui-space-1, --gui-space-2 | The padding of the panel and the headings, and the space between the lists. |
--gui-font-sm | The text size of the headings. |
--gui-text-muted | The headings, and the message of an empty list. |
--gui-text-default | The times. |
--gui-intent-primary | The picked time. |
--gui-intent-primary-hover | A hovered time. |
--gui-intent-primary-text | The text of the picked and hovered times. |
--gui-bg-disabled | A time the user can’t pick. |
--gui-border-focus | The focus ring of a time. |
--gui-intent-error | The panel’s border while invalid. |
/* A brand color for the picked times and the pills. */gui-range-time-picker { --gui-intent-primary: #2f7d6d;}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 “Time list”. - Toggle button. The button that opens the popup is named “Show time list”; change it with
toggle-aria-label. It hasaria-haspopup="dialog", andaria-expandedtells whether the popup is open. - Lists. Each list is a listbox named by its heading, “Start time” and “End time” by default
(
start-time-labelandend-time-label). Each time is an option; the picked one hasaria-selected="true"and unavailable ones havearia-disabled="true". - Start, end and parts. As in the Time range field: the start and end are named “Start time” and “End 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 lists. Leaving the picker closes the popup. |
| Focus on a part | Opens the popup, and keeps the focus in the field. |
| ↑ ↓, on a part | Without allow-custom-time, step the start or the end to the previous or next time of its list. |
| Enter, on a part | Adds the range. |
| Enter Space | On the toggle button, opens the popup and moves focus to the start list, or closes it. |
| ↑ ↓, in a list | Move to the previous or next time, skipping unavailable ones. Home and End move to the first and last. |
| Enter Space, in a list | Pick the focused time. |
| Escape | Closes the popup and moves focus back to the field. |
The keys of the field’s pills are on the Date range page. What every element shares, such as translating the default names, is in Accessibility.