Skip to content
Components, switch product

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.

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

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.

MeetingTimeField.tsx
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 ?? '')}
/>
);
}

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.

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

Code of the example above
TimePickerValue.tsx
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>
</>
);
}

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.

Code of the example above
TimePickerStepBounds.tsx
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"
/>
);
}

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.

Code of the example above
TimePickerDisabledRanges.tsx
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}
/>
);
}

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.

Code of the example above
TimePickerCustom.tsx
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([])}
/>
);
}

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.

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.

Code of the example above
TimePickerGrid.tsx
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}
/>
);
}

disabled and icon work as on the Time input; a readonly picker opens its list but doesn’t change.

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.

Code of the example above
TimePickerNativeForm.tsx
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>
);
}

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

<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.

Code of the example above
TimePickerTimeList.tsx
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>
</>
);
}

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.

<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
<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.

ClassPart
gui-label, gui-label__textThe label and its text with the required marker.
gui-widget-hintThe hint.
gui-widgetThe box around the field, the button and the panel.
gui-time-picker__arrowThe button that opens and closes the list. Its caret turns while the list is open.
gui-picker__panelThe floating panel around the list.
gui-time-list__viewportThe list, which scrolls. --grid when it has columns.
gui-time-list__rowA row of times in a grid.
gui-time-list__optionA time. --selected on the value.
gui-time-list__emptyThe text shown instead of the list when no time can be picked.
gui-validatorThe errors container under the field. The alert role announces new errors.
gui-validator--panelA copy of the errors at the bottom of the open panel, hidden from screen readers.
gui-validator__errorOne error message.

The field’s classes are on the Time input page.

The field reads the tokens of the Time input. The panel and the list add these:

TokenWhat it styles
--gui-bg-defaultThe background of the panel and the list.
--gui-border-defaultThe border of the panel and the list.
--gui-radius-mdThe corner radius of the panel, the list and a time in a grid.
--gui-shadow-mdThe panel’s shadow.
--gui-space-2The panel’s padding.
--gui-space-1The gaps of a grid of times.
--gui-text-defaultThe times.
--gui-intent-primaryThe background of the selected time.
--gui-intent-primary-hoverThe background of a time under the mouse.
--gui-intent-primary-textThe text of the selected time and of a time under the mouse.
--gui-bg-disabledThe times that can’t be picked.
--gui-border-focusThe focus ring of a time.
--gui-text-mutedThe text shown when no time can be picked.
--gui-intent-errorThe panel’s border while there are errors.

The height of the list and of each time come from the height and item-height attributes.

styles.css
/* 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.

  • Name. The label names the field’s group, the popup and the list. Without a label the popup is named Time list.
  • Toggle. The button at the end is named Show time list, has aria-haspopup="dialog", reports aria-expanded and points at the popup with aria-controls. Rename it with toggle-aria-label, or translate it for every element under the key showTimeList.
  • 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 listbox role, or grid with columns. Each time is an option, or a grid cell, with aria-selected; a disabled time has aria-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:

KeyAction
TabMoves 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.
EnterCommits the time and closes the list. In a form, it submits the form too.
EscapeCloses the list.
Enter / Space on the buttonOpens 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:

KeyAction
↑ / ↓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 / EndMove to the first or last time that can be picked.
Page Up / Page DownMove up or down by the times that fit in the list’s height.
Enter / SpacePicks the time. In the picker, it also closes the list and returns to the field.
EscapeIn 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.