Skip to content
Components, switch product

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.

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

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.

MeetingField.tsx
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 ?? [])}
/>
);
}

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.

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.

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.

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

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.

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.

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.

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

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.

allow-custom-time lets the user type any time in the parts, not only the times of the lists.

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

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.

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

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.

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

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

<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
<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>
ClassPart
gui-label, gui-label__textThe label, and its text with the required marker.
gui-widgetThe box around the field, the button and the panel.
gui-range-time-picker__arrowThe button that opens and closes the panel. Its gui-caret turns while the panel is open.
gui-picker__panel, gui-range-time-picker__panelThe popup. While there are errors, it repeats them at its bottom.
gui-range-time-picker__columnsThe two lists, side by side.
gui-range-time-picker__columnOne list with its heading, gui-range-time-picker__column-label.
gui-range-time-picker__listA list, a <gui-time-list> element. gui-time-list__option is one time, with gui-time-list__option--selected on the picked one.
gui-validatorThe 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.

The field reads the tokens of the Date range field. The panel and its lists read these:

TokenWhat it styles
--gui-bg-defaultThe background of the panel and the lists.
--gui-border-defaultThe border of the panel and the lists.
--gui-radius-mdThe corner radius of the panel and the lists.
--gui-shadow-mdThe shadow of the panel.
--gui-space-1, --gui-space-2The padding of the panel and the headings, and the space between the lists.
--gui-font-smThe text size of the headings.
--gui-text-mutedThe headings, and the message of an empty list.
--gui-text-defaultThe times.
--gui-intent-primaryThe picked time.
--gui-intent-primary-hoverA hovered time.
--gui-intent-primary-textThe text of the picked and hovered times.
--gui-bg-disabledA time the user can’t pick.
--gui-border-focusThe focus ring of a time.
--gui-intent-errorThe panel’s border while invalid.
styles.css
/* 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.

  • Name. The label names 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 has aria-haspopup="dialog", and aria-expanded tells whether the popup is open.
  • Lists. Each list is a listbox named by its heading, “Start time” and “End time” by default (start-time-label and end-time-label). Each time is an option; the picked one has aria-selected="true" and unavailable ones have aria-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.
KeyAction
TabMoves 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 partOpens the popup, and keeps the focus in the field.
↑ ↓, on a partWithout allow-custom-time, step the start or the end to the previous or next time of its list.
Enter, on a partAdds the range.
Enter SpaceOn the toggle button, opens the popup and moves focus to the start list, or closes it.
↑ ↓, in a listMove to the previous or next time, skipping unavailable ones. Home and End move to the first and last.
Enter Space, in a listPick the focused time.
EscapeCloses 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.