Time picker
<gui-time-picker> is a time field with a list of times in a popup. By default the user picks
the time from the list, like a select; with allow-custom-time they can also type it, as in a
Time input. Its value is an ISO time, such as 10:30:00.
import { GuiTimePicker } from '@golemui/gui-components/react';import '@golemui/gui-components/index.css';
export function TimePickerBasic() { return <GuiTimePicker label="Meeting time" />;}import { Component, CUSTOM_ELEMENTS_SCHEMA } from '@angular/core';import '@golemui/gui-components/time-picker';
@Component({ selector: 'app-time-picker-basic', schemas: [CUSTOM_ELEMENTS_SCHEMA], template: `<gui-time-picker label="Meeting time"></gui-time-picker>`,})export class TimePickerBasicComponent {}<script setup lang="ts">import '@golemui/gui-components/time-picker';import '@golemui/gui-components/index.css';</script>
<template> <gui-time-picker label="Meeting time"></gui-time-picker></template>import { LitElement, html } from 'lit';import { customElement } from 'lit/decorators.js';import '@golemui/gui-components/time-picker';import '@golemui/gui-components/index.css';
@customElement('time-picker-basic')export class TimePickerBasic extends LitElement { protected override createRenderRoot() { return this; }
override render() { return html`<gui-time-picker label="Meeting time"></gui-time-picker>`; }}<gui-time-picker label="Meeting time"></gui-time-picker>The element’s entry point is @golemui/gui-components/time-picker, which registers
<gui-time-picker> and the <gui-time> and <gui-time-list> it is made of. 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 { GuiTimePicker } from '@golemui/gui-components/react';import '@golemui/gui-components/index.css';
export function MeetingTimeField() { const [meetingTime, setMeetingTime] = useState('');
return ( <GuiTimePicker label="Meeting time" name="meetingTime" value={meetingTime} onGuiChange={(event) => setMeetingTime(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, GuiTimePickerEvents } from '@golemui/gui-components';import '@golemui/gui-components/time-picker';
type ChangeEvent = GuiEventMap<typeof GuiTimePickerEvents>['gui-change'];
@Component({ selector: 'app-meeting-time-field', schemas: [CUSTOM_ELEMENTS_SCHEMA], template: ` <gui-time-picker label="Meeting time" name="meetingTime" [value]="meetingTime" (gui-change)="onChange($event)" ></gui-time-picker> `,})export class MeetingTimeFieldComponent { meetingTime = '';
onChange(event: Event) { this.meetingTime = (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/time-picker';import '@golemui/gui-components/index.css';
const meetingTime = ref('');</script>
<template> <gui-time-picker label="Meeting time" name="meetingTime" :value="meetingTime" @gui-change="meetingTime = $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/time-picker';import '@golemui/gui-components/index.css';
@customElement('meeting-time-field')export class MeetingTimeField extends LitElement { @state() private meetingTime = '';
protected override createRenderRoot() { return this; }
override render() { return html` <gui-time-picker label="Meeting time" name="meetingTime" .value=${this.meetingTime} @gui-change=${(event: CustomEvent) => (this.meetingTime = event.detail.value)} ></gui-time-picker> `; }}With any bundler, such as Vite. Import the element once, anywhere, and use the tag in your markup.
<gui-time-picker label="Meeting time" name="meetingTime"></gui-time-picker>
<script type="module" src="./main.js"></script>import '@golemui/gui-components/time-picker';import '@golemui/gui-components/index.css';
const element = document.querySelector('gui-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 | string | – | The time, as an ISO time (HH:mm:ss). |
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 the time list, in pixels. |
itemHeight
attribute item-height | number | – | Height of each time in the list, in pixels. |
columns | number | – | Number of columns of the time grid. |
minTimeMessage
attribute min-time-message | string | – | Error for a time before minTime. |
maxTimeMessage
attribute max-time-message | string | – | Error for a time after maxTime. |
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. |
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 picked a time from the list or pressed Enter. detail.value is the time. |
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. |
gui-list-toggle | The time list opened or closed. detail.open is its new state. |
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. |
Examples
Section titled “Examples”Value and events
Section titled “Value and events”value takes an ISO time (HH:mm:ss) on a 24-hour clock, whatever the field shows. The list opens
when the field gets focus, or from the button at the end of the field, scrolled to the value. It
closes when the user picks a time, presses Escape, clicks outside or moves focus out
of the element; picking a time and Escape return focus to the field.
While the field has focus, ↑ and ↓ move the value to the
previous or next time of the list and fire gui-input. A pick from the list, or Enter
in the field, fires gui-input and gui-change. gui-list-toggle fires each time the list opens
or closes, with event.detail.open.
Value: 10:30:00
import { useState } from 'react';import { GuiTimePicker } from '@golemui/gui-components/react';import '@golemui/gui-components/index.css';
export function TimePickerValue() { const [value, setValue] = useState<string | undefined>('10:30:00');
return ( <> <GuiTimePicker id="time-picker-pickup" label="Pickup time" value={value} onGuiChange={(event) => setValue(event.detail.value)} /> <p role="status">Value: {value}</p> </> );}import { Component, CUSTOM_ELEMENTS_SCHEMA, signal } from '@angular/core';import type { GuiEventMap, GuiTimePickerEvents } from '@golemui/gui-components';import '@golemui/gui-components/time-picker';
type TimePickerEvent<K extends keyof typeof GuiTimePickerEvents> = GuiEventMap< typeof GuiTimePickerEvents>[K];
@Component({ selector: 'app-time-picker-value', schemas: [CUSTOM_ELEMENTS_SCHEMA], template: ` <gui-time-picker id="time-picker-pickup" label="Pickup time" [value]="value()" (gui-change)="onChange($event)" ></gui-time-picker> <p id="time-picker-pickup-value" role="status">Value: {{ value() }}</p> `,})export class TimePickerValueComponent { value = signal<string | undefined>('10:30:00');
onChange(event: Event) { this.value.set((event as TimePickerEvent<'gui-change'>).detail.value); }}<script setup lang="ts">import { ref } from 'vue';import '@golemui/gui-components/time-picker';import '@golemui/gui-components/index.css';
const value = ref<string | undefined>('10:30:00');</script>
<template> <gui-time-picker id="time-picker-pickup" label="Pickup time" :value="value" @gui-change="value = $event.detail.value" ></gui-time-picker> <p id="time-picker-pickup-value" role="status">Value: {{ value }}</p></template>import { LitElement, html } from 'lit';import { customElement, state } from 'lit/decorators.js';import type { GuiEventMap, GuiTimePickerEvents } from '@golemui/gui-components';import '@golemui/gui-components/time-picker';import '@golemui/gui-components/index.css';
type TimePickerEvent<K extends keyof typeof GuiTimePickerEvents> = GuiEventMap< typeof GuiTimePickerEvents>[K];
@customElement('time-picker-value')export class TimePickerValue extends LitElement { @state() private value: string | undefined = '10:30:00';
protected override createRenderRoot() { return this; }
private onChange(event: TimePickerEvent<'gui-change'>) { this.value = event.detail.value; }
override render() { return html` <gui-time-picker id="time-picker-pickup" label="Pickup time" .value=${this.value} @gui-change=${this.onChange} ></gui-time-picker> <p role="status">Value: ${this.value}</p> `; }}<gui-time-picker id="time-picker-pickup" label="Pickup time" value="10:30:00"></gui-time-picker><p id="time-picker-pickup-value" role="status">Value: 10:30:00</p>
<script type="module"> const pickup = document.querySelector('#time-picker-pickup');
pickup.addEventListener('gui-change', (event) => { document.querySelector('#time-picker-pickup-value').textContent = `Value: ${event.detail.value}`; });</script>Minute step and bounds
Section titled “Minute step and bounds”minute-step sets the minutes between the times of the list: 30 by default. min-time and
max-time take ISO times and are inclusive: the list starts and ends at them. Without them, it
covers the whole day. locale-id and hour-format set the clock of the field and the list, as on
the Time input.
import { GuiTimePicker } from '@golemui/gui-components/react';import '@golemui/gui-components/index.css';
export function TimePickerStepBounds() { return ( <GuiTimePicker label="Appointment" hint="Every 15 minutes, from 9:00 AM to 5:00 PM." minuteStep={15} minTime="09:00:00" maxTime="17:00:00" /> );}import { Component, CUSTOM_ELEMENTS_SCHEMA } from '@angular/core';import '@golemui/gui-components/time-picker';
@Component({ selector: 'app-time-picker-step-bounds', schemas: [CUSTOM_ELEMENTS_SCHEMA], template: ` <gui-time-picker label="Appointment" hint="Every 15 minutes, from 9:00 AM to 5:00 PM." [minuteStep]="15" min-time="09:00:00" max-time="17:00:00" ></gui-time-picker> `,})export class TimePickerStepBoundsComponent {}<script setup lang="ts">import '@golemui/gui-components/time-picker';import '@golemui/gui-components/index.css';</script>
<template> <gui-time-picker label="Appointment" hint="Every 15 minutes, from 9:00 AM to 5:00 PM." :minute-step="15" min-time="09:00:00" max-time="17:00:00" ></gui-time-picker></template>import { LitElement, html } from 'lit';import { customElement } from 'lit/decorators.js';import '@golemui/gui-components/time-picker';import '@golemui/gui-components/index.css';
@customElement('time-picker-step-bounds')export class TimePickerStepBounds extends LitElement { protected override createRenderRoot() { return this; }
override render() { return html` <gui-time-picker label="Appointment" hint="Every 15 minutes, from 9:00 AM to 5:00 PM." minute-step="15" min-time="09:00:00" max-time="17:00:00" ></gui-time-picker> `; }}<gui-time-picker label="Appointment" hint="Every 15 minutes, from 9:00 AM to 5:00 PM." minute-step="15" min-time="09:00:00" max-time="17:00:00"></gui-time-picker>Disabled times
Section titled “Disabled times”disabled-ranges takes a JSON list of { "start", "end" } ISO times. The times inside them,
both ends included, stay in the list but can’t be picked, and the arrow keys skip them.
import { GuiTimePicker } from '@golemui/gui-components/react';import type { TimeRange } from '@golemui/gui-components';import '@golemui/gui-components/index.css';
const lunch: TimeRange[] = [{ start: '12:00:00', end: '14:00:00' }];
export function TimePickerDisabledRanges() { return ( <GuiTimePicker label="Delivery slot" hint="No deliveries over lunch." minuteStep={60} minTime="08:00:00" maxTime="18:00:00" disabledRanges={lunch} /> );}import { Component, CUSTOM_ELEMENTS_SCHEMA } from '@angular/core';import type { TimeRange } from '@golemui/gui-components';import '@golemui/gui-components/time-picker';
@Component({ selector: 'app-time-picker-disabled-ranges', schemas: [CUSTOM_ELEMENTS_SCHEMA], template: ` <gui-time-picker label="Delivery slot" hint="No deliveries over lunch." [minuteStep]="60" min-time="08:00:00" max-time="18:00:00" [disabledRanges]="lunch" ></gui-time-picker> `,})export class TimePickerDisabledRangesComponent { lunch: TimeRange[] = [{ start: '12:00:00', end: '14:00:00' }];}<script setup lang="ts">import type { TimeRange } from '@golemui/gui-components';import '@golemui/gui-components/time-picker';import '@golemui/gui-components/index.css';
const disabledRanges: TimeRange[] = [{ start: '12:00:00', end: '14:00:00' }];</script>
<template> <gui-time-picker label="Delivery slot" hint="No deliveries over lunch." :minute-step="60" min-time="08:00:00" max-time="18:00:00" :disabledRanges.prop="disabledRanges" ></gui-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/time-picker';import '@golemui/gui-components/index.css';
const lunch: TimeRange[] = [{ start: '12:00:00', end: '14:00:00' }];
@customElement('time-picker-disabled-ranges')export class TimePickerDisabledRanges extends LitElement { protected override createRenderRoot() { return this; }
override render() { return html` <gui-time-picker label="Delivery slot" hint="No deliveries over lunch." minute-step="60" min-time="08:00:00" max-time="18:00:00" .disabledRanges=${lunch} ></gui-time-picker> `; }}<gui-time-picker label="Delivery slot" hint="No deliveries over lunch." minute-step="60" min-time="08:00:00" max-time="18:00:00" disabled-ranges='[{ "start": "12:00:00", "end": "14:00:00" }]'></gui-time-picker>Typing a time
Section titled “Typing a time”allow-custom-time lets the user type any time in the field, as in a
Time input, as well as pick one. A typed time outside the bounds
or in a disabled range is kept as the value, marked invalid for its form, and reported with
gui-input-error. Show the message with errors, as below. Type 06:45 to see it.
import { useState } from 'react';import { GuiTimePicker } from '@golemui/gui-components/react';import '@golemui/gui-components/index.css';
export function TimePickerCustom() { const [errors, setErrors] = useState<string[]>([]);
return ( <GuiTimePicker id="time-picker-arrival" label="Arrival time" hint="Pick a time, or type any time." minuteStep={30} minTime="07:00:00" maxTime="22:00:00" allowCustomTime errors={errors} // The element reports what it rejects; show it with errors. onGuiInputError={(event) => setErrors([event.detail.message])} onGuiInput={() => setErrors([])} /> );}import { Component, CUSTOM_ELEMENTS_SCHEMA, signal } from '@angular/core';import type { GuiEventMap, GuiTimePickerEvents } from '@golemui/gui-components';import '@golemui/gui-components/time-picker';
type TimePickerEvent<K extends keyof typeof GuiTimePickerEvents> = GuiEventMap< typeof GuiTimePickerEvents>[K];
@Component({ selector: 'app-time-picker-custom', schemas: [CUSTOM_ELEMENTS_SCHEMA], template: ` <gui-time-picker id="time-picker-arrival" label="Arrival time" hint="Pick a time, or type any time." [minuteStep]="30" min-time="07:00:00" max-time="22:00:00" allow-custom-time [errors]="errors()" (gui-input-error)="onInputError($event)" (gui-input)="errors.set([])" ></gui-time-picker> `,})export class TimePickerCustomComponent { errors = signal<string[]>([]);
// The element reports what it rejects; show it with errors. onInputError(event: Event) { this.errors.set([ (event as TimePickerEvent<'gui-input-error'>).detail.message, ]); }}<script setup lang="ts">import { ref } from 'vue';import '@golemui/gui-components/time-picker';import '@golemui/gui-components/index.css';
const errors = ref<string[]>([]);</script>
<template> <!-- The element reports what it rejects; show it with errors. --> <gui-time-picker id="time-picker-arrival" label="Arrival time" hint="Pick a time, or type any time." :minute-step="30" min-time="07:00:00" max-time="22:00:00" :allow-custom-time="true" :errors.prop="errors" @gui-input-error="errors = [$event.detail.message]" @gui-input="errors = []" ></gui-time-picker></template>import { LitElement, html } from 'lit';import { customElement, state } from 'lit/decorators.js';import type { GuiEventMap, GuiTimePickerEvents } from '@golemui/gui-components';import '@golemui/gui-components/time-picker';import '@golemui/gui-components/index.css';
type TimePickerEvent<K extends keyof typeof GuiTimePickerEvents> = GuiEventMap< typeof GuiTimePickerEvents>[K];
@customElement('time-picker-custom')export class TimePickerCustom extends LitElement { @state() private errors: string[] = [];
protected override createRenderRoot() { return this; }
// The element reports what it rejects; show it with errors. private onInputError(event: TimePickerEvent<'gui-input-error'>) { this.errors = [event.detail.message]; }
private onInput() { this.errors = []; }
override render() { return html` <gui-time-picker id="time-picker-arrival" label="Arrival time" hint="Pick a time, or type any time." minute-step="30" min-time="07:00:00" max-time="22:00:00" allow-custom-time .errors=${this.errors} @gui-input-error=${this.onInputError} @gui-input=${this.onInput} ></gui-time-picker> `; }}<gui-time-picker id="time-picker-arrival" label="Arrival time" hint="Pick a time, or type any time." minute-step="30" min-time="07:00:00" max-time="22:00:00" allow-custom-time></gui-time-picker>
<script type="module"> const arrival = document.querySelector('#time-picker-arrival');
// The element reports what it rejects; show it with errors. arrival.addEventListener('gui-input-error', (event) => { arrival.errors = [event.detail.message]; }); arrival.addEventListener('gui-input', () => { arrival.errors = []; });</script>Replace the English messages with min-time-message, max-time-message,
disabled-range-message and incomplete-message, and the text of an empty list with
no-available-times-message, or translate them for every element as in
i18n.
Grid of times
Section titled “Grid of times”columns lays the list out as a grid of that many columns. height sets the height of the list
in pixels, 300 by default, and item-height the height of each time, 40 by default.
import { GuiTimePicker } from '@golemui/gui-components/react';import '@golemui/gui-components/index.css';
export function TimePickerGrid() { return ( <GuiTimePicker label="Class starts at" minuteStep={15} minTime="16:00:00" maxTime="20:00:00" columns={4} height={200} /> );}import { Component, CUSTOM_ELEMENTS_SCHEMA } from '@angular/core';import '@golemui/gui-components/time-picker';
@Component({ selector: 'app-time-picker-grid', schemas: [CUSTOM_ELEMENTS_SCHEMA], template: ` <gui-time-picker label="Class starts at" [minuteStep]="15" min-time="16:00:00" max-time="20:00:00" [columns]="4" [height]="200" ></gui-time-picker> `,})export class TimePickerGridComponent {}<script setup lang="ts">import '@golemui/gui-components/time-picker';import '@golemui/gui-components/index.css';</script>
<template> <gui-time-picker label="Class starts at" :minute-step="15" min-time="16:00:00" max-time="20:00:00" :columns="4" :height="200" ></gui-time-picker></template>import { LitElement, html } from 'lit';import { customElement } from 'lit/decorators.js';import '@golemui/gui-components/time-picker';import '@golemui/gui-components/index.css';
@customElement('time-picker-grid')export class TimePickerGrid extends LitElement { protected override createRenderRoot() { return this; }
override render() { return html` <gui-time-picker label="Class starts at" minute-step="15" min-time="16:00:00" max-time="20:00:00" columns="4" height="200" ></gui-time-picker> `; }}<gui-time-picker label="Class starts at" minute-step="15" min-time="16:00:00" max-time="20:00:00" columns="4" height="200"></gui-time-picker>disabled and icon work as on the Time input; a readonly
picker opens its list but doesn’t change.
In a form
Section titled “In a form”With a name, <gui-time-picker> submits its ISO time under that name, such as
reservedAt=20:30:00, and nothing while it is empty. required, a time outside min-time and
max-time and a time in disabled-ranges block a submit.
import { useState } from 'react';import { GuiButton, GuiTimePicker } from '@golemui/gui-components/react';import '@golemui/gui-components/index.css';
export function TimePickerNativeForm() { const [result, setResult] = useState('');
return ( <form id="time-picker-form" onSubmit={(event) => { event.preventDefault(); const data = Object.fromEntries(new FormData(event.currentTarget)); setResult(`Submitted ${JSON.stringify(data)}`); }} > <div className="gui-grid gui-grid--row"> <div className="gui-grid__cell gui-grid__cell--span-3"> <GuiTimePicker label="Table for two at" name="reservedAt" minuteStep={30} minTime="19:00:00" maxTime="22:30:00" 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/time-picker';
@Component({ selector: 'app-time-picker-native-form', schemas: [CUSTOM_ELEMENTS_SCHEMA], template: ` <form id="time-picker-form" #form (submit)="onSubmit($event, form)"> <div class="gui-grid gui-grid--row"> <div class="gui-grid__cell gui-grid__cell--span-3"> <gui-time-picker label="Table for two at" name="reservedAt" [minuteStep]="30" min-time="19:00:00" max-time="22:30:00" required ></gui-time-picker> </div> <div class="gui-grid__cell"> <gui-button type="submit" label="Reserve"></gui-button> </div> </div> <p id="time-picker-form-result" role="status">{{ result() }}</p> </form> `,})export class TimePickerNativeFormComponent { result = signal('');
onSubmit(event: Event, form: HTMLFormElement) { event.preventDefault(); const data = Object.fromEntries(new FormData(form)); this.result.set(`Submitted ${JSON.stringify(data)}`); }}<script setup lang="ts">import { ref, useTemplateRef } from 'vue';import '@golemui/gui-components/button';import '@golemui/gui-components/time-picker';import '@golemui/gui-components/index.css';
const form = useTemplateRef('form');const result = ref('');
function onSubmit() { if (!form.value) return; const data = Object.fromEntries(new FormData(form.value)); result.value = `Submitted ${JSON.stringify(data)}`;}</script>
<template> <form id="time-picker-form" ref="form" @submit.prevent="onSubmit"> <div class="gui-grid gui-grid--row"> <div class="gui-grid__cell gui-grid__cell--span-3"> <gui-time-picker label="Table for two at" name="reservedAt" :minute-step="30" min-time="19:00:00" max-time="22:30:00" :required="true" ></gui-time-picker> </div> <div class="gui-grid__cell"> <gui-button type="submit" label="Reserve"></gui-button> </div> </div> <p id="time-picker-form-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/time-picker';import '@golemui/gui-components/index.css';
@customElement('time-picker-native-form')export class TimePickerNativeForm extends LitElement { @query('form') private form!: HTMLFormElement; @state() private data?: Record<string, FormDataEntryValue>;
protected override createRenderRoot() { return this; }
private onSubmit(event: SubmitEvent) { event.preventDefault(); this.data = Object.fromEntries(new FormData(this.form)); }
override render() { return html` <form id="time-picker-form" @submit=${this.onSubmit}> <div class="gui-grid gui-grid--row"> <div class="gui-grid__cell gui-grid__cell--span-3"> <gui-time-picker label="Table for two at" name="reservedAt" minute-step="30" min-time="19:00:00" max-time="22:30:00" required ></gui-time-picker> </div> <div class="gui-grid__cell"> <gui-button type="submit" label="Reserve"></gui-button> </div> </div> <p role="status"> ${this.data && `Submitted ${JSON.stringify(this.data)}`} </p> </form> `; }}<form id="time-picker-form"> <div class="gui-grid gui-grid--row"> <div class="gui-grid__cell gui-grid__cell--span-3"> <gui-time-picker label="Table for two at" name="reservedAt" minute-step="30" min-time="19:00:00" max-time="22:30:00" required ></gui-time-picker> </div> <div class="gui-grid__cell"> <gui-button type="submit" label="Reserve"></gui-button> </div> </div> <p id="time-picker-form-result" role="status"></p></form>
<script type="module"> const form = document.querySelector('#time-picker-form');
form.addEventListener('submit', (event) => { event.preventDefault(); const data = Object.fromEntries(new FormData(form)); document.querySelector('#time-picker-form-result').textContent = `Submitted ${JSON.stringify(data)}`; });</script>How the elements submit, validate and reset is in Native forms.
gui-time-list
Section titled “gui-time-list”<gui-time-list> is the list of times inside the picker, as an element of its own. Use it alone
for a list of times that is always visible, such as the free slots of a day. It takes the same
minute-step, min-time, max-time, disabled-ranges, locale-id, hour-format, columns,
height and item-height. Its label names the list. A pick fires gui-input and gui-change
with the ISO time. It is not a form control: it has no name and doesn’t submit.
Its entry point is @golemui/gui-components/time-list.
import { useState } from 'react';import { GuiTimeList } from '@golemui/gui-components/react';import type { TimeRange } from '@golemui/gui-components';import '@golemui/gui-components/index.css';
const taken: TimeRange[] = [{ start: '15:00:00', end: '15:30:00' }];
export function TimePickerTimeList() { const [value, setValue] = useState<string>();
return ( <> <GuiTimeList id="time-list-slots" label="Free slots" minuteStep={30} minTime="14:00:00" maxTime="17:00:00" disabledRanges={taken} height={200} value={value} onGuiChange={(event) => setValue(event.detail.value)} /> <p role="status">{value === undefined ? '' : `Value: ${value}`}</p> </> );}import { Component, CUSTOM_ELEMENTS_SCHEMA, signal } from '@angular/core';import type { GuiEventMap, GuiTimeListEvents, TimeRange,} from '@golemui/gui-components';import '@golemui/gui-components/time-list';
type TimeListEvent<K extends keyof typeof GuiTimeListEvents> = GuiEventMap< typeof GuiTimeListEvents>[K];
@Component({ selector: 'app-time-picker-time-list', schemas: [CUSTOM_ELEMENTS_SCHEMA], template: ` <gui-time-list id="time-list-slots" label="Free slots" [minuteStep]="30" min-time="14:00:00" max-time="17:00:00" [disabledRanges]="taken" [height]="200" (gui-change)="onChange($event)" ></gui-time-list> <p id="time-list-slots-value" role="status">{{ status() }}</p> `,})export class TimePickerTimeListComponent { taken: TimeRange[] = [{ start: '15:00:00', end: '15:30:00' }];
status = signal('');
onChange(event: Event) { const { value } = (event as TimeListEvent<'gui-change'>).detail; this.status.set(`Value: ${value}`); }}<script setup lang="ts">import { ref } from 'vue';import type { TimeRange } from '@golemui/gui-components';import '@golemui/gui-components/time-list';import '@golemui/gui-components/index.css';
const disabledRanges: TimeRange[] = [{ start: '15:00:00', end: '15:30:00' }];const status = ref('');</script>
<template> <gui-time-list id="time-list-slots" label="Free slots" :minute-step="30" min-time="14:00:00" max-time="17:00:00" :disabledRanges.prop="disabledRanges" :height="200" @gui-change="status = `Value: ${$event.detail.value}`" ></gui-time-list> <p id="time-list-slots-value" role="status">{{ status }}</p></template>import { LitElement, html } from 'lit';import { customElement, state } from 'lit/decorators.js';import type { GuiEventMap, GuiTimeListEvents, TimeRange,} from '@golemui/gui-components';import '@golemui/gui-components/time-list';import '@golemui/gui-components/index.css';
type TimeListEvent<K extends keyof typeof GuiTimeListEvents> = GuiEventMap< typeof GuiTimeListEvents>[K];
const booked: TimeRange[] = [{ start: '15:00:00', end: '15:30:00' }];
@customElement('time-picker-time-list')export class TimePickerTimeList extends LitElement { @state() private value?: string;
protected override createRenderRoot() { return this; }
private onChange(event: TimeListEvent<'gui-change'>) { this.value = event.detail.value; }
override render() { return html` <gui-time-list id="time-list-slots" label="Free slots" minute-step="30" min-time="14:00:00" max-time="17:00:00" .disabledRanges=${booked} height="200" .value=${this.value} @gui-change=${this.onChange} ></gui-time-list> <p role="status">${this.value && `Value: ${this.value}`}</p> `; }}<gui-time-list id="time-list-slots" label="Free slots" minute-step="30" min-time="14:00:00" max-time="17:00:00" disabled-ranges='[{ "start": "15:00:00", "end": "15:30:00" }]' height="200"></gui-time-list><p id="time-list-slots-value" role="status"></p>
<script type="module"> const slots = document.querySelector('#time-list-slots');
slots.addEventListener('gui-change', (event) => { document.querySelector('#time-list-slots-value').textContent = `Value: ${event.detail.value}`; });</script>Properties
| Property | Type | Default | Description |
|---|---|---|---|
value | string | – | The selected time, as an ISO time (HH:mm:ss). |
label | string | – | Accessible name of the list. An empty value keeps the default. |
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). |
minuteStep
attribute minute-step | number | – | Minutes between the times offered in the list. |
disabledRanges
attribute disabled-ranges | TimeRange[] | – | Times that cannot be picked, as { start, end } ISO time ranges. |
localeId
attribute locale-id | string | – | BCP 47 locale for formatting and parsing, such as en-US or es. |
hourFormat
attribute hour-format | HourFormat | – | 12- or 24-hour clock. Defaults to the locale's. |
disabled | boolean | false | Disables the list. |
readOnly
attribute readonly | boolean | false | Shows the times without letting the user pick one. |
height | number | – | Height of the scrollable list, in pixels. |
itemHeight
attribute item-height | number | – | Height of each item, in pixels. Needed to virtualize the list. |
columns | number | – | Number of columns of the time grid. |
noAvailableTimesMessage
attribute no-available-times-message | string | – | Text shown when no time can be picked. |
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. |
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 committed the value. detail.value is the committed value. |
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. |
Styling
Section titled “Styling”Anatomy
Section titled “Anatomy”<gui-time-picker> renders into the light DOM, so you can style every part with plain CSS. It is
a <gui-time>, a toggle button and a panel with a <gui-time-list>. Here the list is open, with
en-US parts:
<gui-time-picker> <span class="gui-label" id="meeting_label"> <span class="gui-label__text">Meeting time</span> </span> <div class="gui-widget"> <gui-time id="meeting_time" class="gui-field" readonly> <div class="gui-widget"> <div role="group" id="meeting" class="gui-widget-input gui-parts gui-parts-ring gui-time-input" aria-labelledby="meeting_label" > <!-- The hour, minute and AM/PM parts, as in gui-time, read-only without allow-custom-time --> </div> </div> </gui-time> <button type="button" class="gui-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-time-list role="dialog" id="meeting_popup" aria-labelledby="meeting_label" > <div class="gui-time-list__viewport" role="listbox" aria-label="Meeting time" > <button type="button" role="option" class="gui-time-list__option" tabindex="-1" data-value="09:00:00" aria-selected="false" aria-disabled="false" > 09:00 AM </button> <button type="button" role="option" class="gui-time-list__option gui-time-list__option--selected" tabindex="0" data-value="09:30:00" aria-selected="true" aria-disabled="false" > 09:30 AM </button> <!-- The other times --> </div> </gui-time-list> </div> </div> <div class="gui-validator" id="meeting_errors" role="alert"></div></gui-time-picker>The panel and the list stay in the page while the list is closed, with hidden. With columns,
the viewport has the grid role and the class gui-time-list__viewport--grid, each row of times
is a gui-time-list__row with the row role, and each time a gridcell.
| Class | Part |
|---|---|
gui-label, gui-label__text | The label and its text with the required marker. |
gui-widget-hint | The hint. |
gui-widget | The box around the field, the button and the panel. |
gui-time-picker__arrow | The button that opens and closes the list. Its caret turns while the list is open. |
gui-picker__panel | The floating panel around the list. |
gui-time-list__viewport | The list, which scrolls. --grid when it has columns. |
gui-time-list__row | A row of times in a grid. |
gui-time-list__option | A time. --selected on the value. |
gui-time-list__empty | The text shown instead of the list when no time can be picked. |
gui-validator | The errors container under the field. The alert role announces new errors. |
gui-validator--panel | A copy of the errors at the bottom of the open panel, hidden from screen readers. |
gui-validator__error | One error message. |
The field’s classes are on the Time input page.
Tokens
Section titled “Tokens”The field reads the tokens of the Time input. The panel and the list add these:
| Token | What it styles |
|---|---|
--gui-bg-default | The background of the panel and the list. |
--gui-border-default | The border of the panel and the list. |
--gui-radius-md | The corner radius of the panel, the list and a time in a grid. |
--gui-shadow-md | The panel’s shadow. |
--gui-space-2 | The panel’s padding. |
--gui-space-1 | The gaps of a grid of times. |
--gui-text-default | The times. |
--gui-intent-primary | The background of the selected time. |
--gui-intent-primary-hover | The background of a time under the mouse. |
--gui-intent-primary-text | The text of the selected time and of a time under the mouse. |
--gui-bg-disabled | The times that can’t be picked. |
--gui-border-focus | The focus ring of a time. |
--gui-text-muted | The text shown when no time can be picked. |
--gui-intent-error | The panel’s border while there are errors. |
The height of the list and of each time come from the height and item-height attributes.
/* The selected time in the brand color. */gui-time-picker { --gui-intent-primary: #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’s group, the popup and the list. Without a label the popup is namedTime list. - Toggle. The button at the end is named
Show time list, hasaria-haspopup="dialog", reportsaria-expandedand points at the popup witharia-controls. Rename it withtoggle-aria-label, or translate it for every element under the keyshowTimeList. - Field. Without
allow-custom-time, the hour and minute are read-only spin buttons, and the arrow keys change the time through the list. The parts are named as on the Time input. - List. The list has the
listboxrole, orgridwithcolumns. Each time is an option, or a grid cell, witharia-selected; a disabled time hasaria-disabled="true". The list is one tab stop, on the selected time or the first one that can be picked.
In the field, without allow-custom-time:
| Key | Action |
|---|---|
| Tab | Moves from the field to the button, then into the open list, then out. |
| ↑ / ↓ | Select the previous or next time of the list, skipping disabled times. |
| Enter | Commits the time and closes the list. In a form, it submits the form too. |
| Escape | Closes the list. |
| Enter / Space on the button | Opens the list and focuses the selected time, or closes it. |
With allow-custom-time, the field takes the keys of the
Time input instead of the arrows above. In the
list, alone or in the picker:
| Key | Action |
|---|---|
| ↑ / ↓ | Move to the previous or next time, or a row up or down in a grid. |
| ← / → | In a grid, move to the previous or next time, mirrored right to left. |
| Home / End | Move to the first or last time that can be picked. |
| Page Up / Page Down | Move up or down by the times that fit in the list’s height. |
| Enter / Space | Picks the time. In the picker, it also closes the list and returns to the field. |
| Escape | In the picker, closes the list and returns focus to the field. |
Moving skips the times that can’t be picked. What every element shares, such as translating the browser messages, is in Accessibility.