Date range picker
<gui-range-date-picker> is a date range field with a calendar in a popup. The user types the
start and end dates, or picks them in the calendar, and each range becomes a pill. The field holds
as many ranges as the user adds.
import { GuiRangeDatePicker } from '@golemui/gui-components/react';import '@golemui/gui-components/index.css';
export function RangeDatePickerBasic() { return <GuiRangeDatePicker label="Trip dates" />;}import { Component, CUSTOM_ELEMENTS_SCHEMA } from '@angular/core';import '@golemui/gui-components/range-date-picker';
@Component({ selector: 'app-range-date-picker-basic', schemas: [CUSTOM_ELEMENTS_SCHEMA], template: `<gui-range-date-picker label="Trip dates" ></gui-range-date-picker>`,})export class RangeDatePickerBasicComponent {}<script setup lang="ts">import '@golemui/gui-components/range-date-picker';import '@golemui/gui-components/index.css';</script>
<template> <gui-range-date-picker label="Trip dates"></gui-range-date-picker></template>import { LitElement, html } from 'lit';import { customElement } from 'lit/decorators.js';import '@golemui/gui-components/range-date-picker';import '@golemui/gui-components/index.css';
@customElement('range-date-picker-basic')export class RangeDatePickerBasic extends LitElement { protected override createRenderRoot() { return this; }
override render() { return html` <gui-range-date-picker label="Trip dates"></gui-range-date-picker> `; }}<gui-range-date-picker label="Trip dates"></gui-range-date-picker>It combines a Date range field and a Range calendar. For a single date, use the Date picker.
The element’s entry point is @golemui/gui-components/range-date-picker, which registers
<gui-range-date-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 { GuiRangeDatePicker } from '@golemui/gui-components/react';import '@golemui/gui-components/index.css';
export function TripsField() { const [trips, setTrips] = useState([]);
return ( <GuiRangeDatePicker label="Trip dates" name="trips" value={trips} onGuiChange={(event) => setTrips(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, GuiRangeDatePickerEvents } from '@golemui/gui-components';import '@golemui/gui-components/range-date-picker';
type ChangeEvent = GuiEventMap<typeof GuiRangeDatePickerEvents>['gui-change'];
@Component({ selector: 'app-trips-field', schemas: [CUSTOM_ELEMENTS_SCHEMA], template: ` <gui-range-date-picker label="Trip dates" name="trips" [value]="trips" (gui-change)="onChange($event)" ></gui-range-date-picker> `,})export class TripsFieldComponent { trips = [];
onChange(event: Event) { this.trips = (event as ChangeEvent).detail.value ?? []; }}"styles": ["node_modules/@golemui/gui-components/lib/styles/index.css"]Tell Vue's compiler that gui-* tags are custom elements, and add the typings once for typed props and @gui-* handlers.
<script setup lang="ts">import { ref } from 'vue';import '@golemui/gui-components/range-date-picker';import '@golemui/gui-components/index.css';
const trips = ref([]);</script>
<template> <gui-range-date-picker label="Trip dates" name="trips" :value="trips" @gui-change="trips = $event.detail.value ?? []" /></template>vue({ template: { compilerOptions: { isCustomElement: (tag) => tag.startsWith('gui-') } },});import type {} from '@golemui/gui-components/vue';The page's stylesheet styles the gui-* elements, so it only reaches them outside a shadow root. Render the host component into the light DOM too, as below, or load the stylesheet into its shadow root.
import { LitElement, html } from 'lit';import { customElement, state } from 'lit/decorators.js';import '@golemui/gui-components/range-date-picker';import '@golemui/gui-components/index.css';
@customElement('trips-field')export class TripsField extends LitElement { @state() private trips = [];
protected override createRenderRoot() { return this; }
override render() { return html` <gui-range-date-picker label="Trip dates" name="trips" .value=${this.trips} @gui-change=${(event: CustomEvent) => (this.trips = event.detail.value)} ></gui-range-date-picker> `; }}With any bundler, such as Vite. Import the element once, anywhere, and use the tag in your markup.
<gui-range-date-picker label="Trip dates" name="trips"></gui-range-date-picker>
<script type="module" src="./main.js"></script>import '@golemui/gui-components/range-date-picker';import '@golemui/gui-components/index.css';
const element = document.querySelector('gui-range-date-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. |
dayAriaLabel
attribute day-aria-label | string | – | Accessible name of the day part. An empty value keeps the default. |
monthAriaLabel
attribute month-aria-label | string | – | Accessible name of the month part. An empty value keeps the default. |
yearAriaLabel
attribute year-aria-label | string | – | Accessible name of the year part. An empty value keeps the default. |
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 | DateRange[] | [] | The date ranges, as { start, end } ISO dates. |
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. |
startDateAriaLabel
attribute start-date-aria-label | string | – | Accessible name of the start date field. An empty value removes it. |
endDateAriaLabel
attribute end-date-aria-label | string | – | Accessible name of the end date field. An empty value removes it. |
prevMonthIcon
attribute prev-month-icon | string | '' | Icon class name of the previous-month button. |
nextMonthIcon
attribute next-month-icon | string | '' | Icon class name of the next-month button. |
prevMonthAriaLabel
attribute prev-month-aria-label | string | – | Accessible name of the previous-month button. An empty value keeps the default. |
nextMonthAriaLabel
attribute next-month-aria-label | string | – | Accessible name of the next-month button. An empty value keeps the default. |
selectYearAriaLabel
attribute select-year-aria-label | string | – | Accessible name of the button that opens the year grid. An empty value keeps the default. |
yearGridAriaLabel
attribute year-grid-aria-label | string | – | Accessible name of the year grid. An empty value keeps the default. |
dayFormat
attribute day-format | 'numeric' | '2-digit' | – | How day numbers are written. |
weekdayFormat
attribute weekday-format | 'short' | 'long' | 'narrow' | – | How weekday names are written in the header. |
monthFormat
attribute month-format | 'numeric' | '2-digit' | 'long' | 'short' | 'narrow' | – | How the month is written in the header. |
minDate
attribute min-date | string | – | Earliest selectable date, as an ISO date (YYYY-MM-DD). |
maxDate
attribute max-date | string | – | Latest selectable date, as an ISO date (YYYY-MM-DD). |
disabledRanges
attribute disabled-ranges | DateRange[] | – | Dates that cannot be picked, as { start, end } ISO date ranges. |
numberOfMonths
attribute number-of-months | number | – | Number of months shown side by side. |
invalidDateMessage
attribute invalid-date-message | string | – | Error for a complete but impossible date, such as February 31. |
minDateMessage
attribute min-date-message | string | – | Error for a date before minDate. |
maxDateMessage
attribute max-date-message | string | – | Error for a date after maxDate. |
disabledDateRangeMessage
attribute disabled-date-range-message | string | – | Error for a date inside disabledRanges. |
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-width | – | Width of one month. |
--gui-calendar-day-button-size | – | Size of each day. |
--gui-calendar-change-month-button-width | – | Width of the previous- and next-month buttons. |
--gui-calendar-change-month-button-height | – | Height of the previous- and next-month buttons. |
--gui-calendar-year-button-width | – | Width of each year in the year grid. |
--gui-calendar-year-button-height | – | Height of each year in the year grid. |
--gui-calendar-year-grid-height | – | Height of the year grid. |
--gui-range-bg | – | Background of the days inside a selected range. |
--gui-range-error-bg | – | Background of the days inside a rejected range. |
--gui-pill-height | – | Height of each pill. |
--gui-pill-font-size | – | Font size of the pill text. |
--gui-pill-action-size | – | Size of the icons inside a pill. |
--gui-pill-action-hit | – | Clickable area of the buttons inside a pill. |
Examples
Section titled “Examples”Picking a range
Section titled “Picking a range”The calendar opens when a part of the field gets focus, or from the button at the end of the field. In the calendar:
- The first day the user picks is one end of the range.
- As the pointer or the focus moves over other days, the calendar previews the range.
- The second day completes the range. It can come before the first one: the range is ordered for you. Picking the same day twice makes a range of one day.
The new range becomes a pill and the calendar stays open for the next one. The days the user picks also fill the parts of the field, and a range typed in the field shows in the calendar.
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 dates
(YYYY-MM-DD); a range of one day has a start only. As an attribute, value is a JSON array.
A range that overlaps or touches another one merges with it, and the pills show in date order.
gui-change fires each time the user adds or removes a range, with the whole list in
event.detail.value.
Bounds and disabled days
Section titled “Bounds and disabled days”min-date and max-date limit the days the user can pick, as ISO dates. disabled-ranges takes
a JSON list of { start, end } ranges the user can’t pick. Out-of-bounds and disabled days show
as disabled in the calendar.
import { GuiRangeDatePicker } from '@golemui/gui-components/react';import type { DateRange } from '@golemui/gui-components';import '@golemui/gui-components/index.css';
const closedWeekends: DateRange[] = [ { start: '2027-03-13', end: '2027-03-14' }, { start: '2027-03-27', end: '2027-03-28' },];
const booking: DateRange[] = [{ start: '2027-03-02', end: '2027-03-05' }];
export function RangeDatePickerBounds() { return ( <GuiRangeDatePicker label="Studio booking" hint="March and April only. The studio is closed on the weekends of March 13 and March 27." minDate="2027-03-01" maxDate="2027-04-30" disabledRanges={closedWeekends} value={booking} /> );}import { Component, CUSTOM_ELEMENTS_SCHEMA } from '@angular/core';import type { DateRange } from '@golemui/gui-components';import '@golemui/gui-components/range-date-picker';
@Component({ selector: 'app-range-date-picker-bounds', schemas: [CUSTOM_ELEMENTS_SCHEMA], template: ` <gui-range-date-picker label="Studio booking" hint="March and April only. The studio is closed on the weekends of March 13 and March 27." min-date="2027-03-01" max-date="2027-04-30" [disabledRanges]="closed" [value]="booking" ></gui-range-date-picker> `,})export class RangeDatePickerBoundsComponent { closed: DateRange[] = [ { start: '2027-03-13', end: '2027-03-14' }, { start: '2027-03-27', end: '2027-03-28' }, ];
booking: DateRange[] = [{ start: '2027-03-02', end: '2027-03-05' }];}<script setup lang="ts">import type { DateRange } from '@golemui/gui-components';import '@golemui/gui-components/range-date-picker';import '@golemui/gui-components/index.css';
const disabledRanges: DateRange[] = [ { start: '2027-03-13', end: '2027-03-14' }, { start: '2027-03-27', end: '2027-03-28' },];const value: DateRange[] = [{ start: '2027-03-02', end: '2027-03-05' }];</script>
<template> <gui-range-date-picker label="Studio booking" hint="March and April only. The studio is closed on the weekends of March 13 and March 27." min-date="2027-03-01" max-date="2027-04-30" :disabledRanges.prop="disabledRanges" :value.prop="value" ></gui-range-date-picker></template>import { LitElement, html } from 'lit';import { customElement } from 'lit/decorators.js';import type { DateRange } from '@golemui/gui-components';import '@golemui/gui-components/range-date-picker';import '@golemui/gui-components/index.css';
const closedWeekends: DateRange[] = [ { start: '2027-03-13', end: '2027-03-14' }, { start: '2027-03-27', end: '2027-03-28' },];
const booking: DateRange[] = [{ start: '2027-03-02', end: '2027-03-05' }];
@customElement('range-date-picker-bounds')export class RangeDatePickerBounds extends LitElement { protected override createRenderRoot() { return this; }
override render() { return html` <gui-range-date-picker label="Studio booking" hint="March and April only. The studio is closed on the weekends of March 13 and March 27." min-date="2027-03-01" max-date="2027-04-30" .disabledRanges=${closedWeekends} .value=${booking} ></gui-range-date-picker> `; }}<gui-range-date-picker label="Studio booking" hint="March and April only. The studio is closed on the weekends of March 13 and March 27." min-date="2027-03-01" max-date="2027-04-30" disabled-ranges='[{"start":"2027-03-13","end":"2027-03-14"},{"start":"2027-03-27","end":"2027-03-28"}]' value='[{"start":"2027-03-02","end":"2027-03-05"}]'></gui-range-date-picker>A range can’t step over a disabled day: a range with a disabled day anywhere inside it is rejected, not only one that starts or ends on it. The calendar shows the rejected range in red, the field shows its dates, and no pill is added. A range typed in the field is checked the same way, against the bounds too.
Errors
Section titled “Errors”When the picker rejects what the user entered, such as a range across a disabled day or a date
like February 31, it fires gui-input-error with the message in event.detail.message. It doesn’t
show the message itself: set it as errors, and clear it on the next gui-input, which fires when
the user adds a range.
import { useState } from 'react';import { GuiRangeDatePicker } from '@golemui/gui-components/react';import type { DateRange } from '@golemui/gui-components';import '@golemui/gui-components/index.css';
const booked: DateRange[] = [{ start: '2027-03-10', end: '2027-03-12' }];
export function RangeDatePickerErrors() { const [value, setValue] = useState<DateRange[] | undefined>([ { start: '2027-03-01', end: '2027-03-03' }, ]); const [errors, setErrors] = useState<string[]>([]);
return ( <GuiRangeDatePicker id="cabin-dates" label="Cabin stay" hint="The cabin is booked from March 10 to March 12. Pick a range across those days." disabledRanges={booked} value={value} errors={errors} onGuiInputError={(event) => setErrors([event.detail.message])} onGuiInput={(event) => { setValue(event.detail.value); setErrors([]); }} /> );}import { Component, CUSTOM_ELEMENTS_SCHEMA, signal } from '@angular/core';import type { DateRange, GuiEventMap, GuiRangeDatePickerEvents,} from '@golemui/gui-components';import '@golemui/gui-components/range-date-picker';
type RangeDatePickerEvent<K extends keyof typeof GuiRangeDatePickerEvents> = GuiEventMap<typeof GuiRangeDatePickerEvents>[K];
@Component({ selector: 'app-range-date-picker-errors', schemas: [CUSTOM_ELEMENTS_SCHEMA], template: ` <gui-range-date-picker id="cabin-dates" label="Cabin stay" hint="The cabin is booked from March 10 to March 12. Pick a range across those days." [disabledRanges]="booked" [value]="stay" [errors]="errors()" (gui-input-error)="onInputError($event)" (gui-input)="errors.set([])" ></gui-range-date-picker> `,})export class RangeDatePickerErrorsComponent { booked: DateRange[] = [{ start: '2027-03-10', end: '2027-03-12' }]; stay: DateRange[] = [{ start: '2027-03-01', end: '2027-03-03' }];
errors = signal<string[]>([]);
onInputError(event: Event) { this.errors.set([ (event as RangeDatePickerEvent<'gui-input-error'>).detail.message, ]); }}<script setup lang="ts">import { ref } from 'vue';import type { DateRange } from '@golemui/gui-components';import '@golemui/gui-components/range-date-picker';import '@golemui/gui-components/index.css';
const disabledRanges: DateRange[] = [ { start: '2027-03-10', end: '2027-03-12' },];const value: DateRange[] = [{ start: '2027-03-01', end: '2027-03-03' }];const errors = ref<string[]>([]);</script>
<template> <gui-range-date-picker id="cabin-dates" label="Cabin stay" hint="The cabin is booked from March 10 to March 12. Pick a range across those days." :disabledRanges.prop="disabledRanges" :value.prop="value" :errors.prop="errors" @gui-input-error="errors = [$event.detail.message]" @gui-input="errors = []" ></gui-range-date-picker></template>import { LitElement, html } from 'lit';import { customElement, state } from 'lit/decorators.js';import type { DateRange, GuiEventMap, GuiRangeDatePickerEvents,} from '@golemui/gui-components';import '@golemui/gui-components/range-date-picker';import '@golemui/gui-components/index.css';
type RangeDatePickerEvent<K extends keyof typeof GuiRangeDatePickerEvents> = GuiEventMap<typeof GuiRangeDatePickerEvents>[K];
const booked: DateRange[] = [{ start: '2027-03-10', end: '2027-03-12' }];
const stay: DateRange[] = [{ start: '2027-03-01', end: '2027-03-03' }];
@customElement('range-date-picker-errors')export class RangeDatePickerErrors extends LitElement { @state() private errors: string[] = [];
protected override createRenderRoot() { return this; }
private onInputError(event: RangeDatePickerEvent<'gui-input-error'>) { this.errors = [event.detail.message]; }
private onInput() { this.errors = []; }
override render() { return html` <gui-range-date-picker id="cabin-dates" label="Cabin stay" hint="The cabin is booked from March 10 to March 12. Pick a range across those days." .disabledRanges=${booked} .value=${stay} .errors=${this.errors} @gui-input-error=${this.onInputError} @gui-input=${this.onInput} ></gui-range-date-picker> `; }}<gui-range-date-picker id="cabin-dates" label="Cabin stay" hint="The cabin is booked from March 10 to March 12. Pick a range across those days." disabled-ranges='[{"start":"2027-03-10","end":"2027-03-12"}]' value='[{"start":"2027-03-01","end":"2027-03-03"}]'></gui-range-date-picker>
<script type="module"> const cabin = document.querySelector('#cabin-dates');
cabin.addEventListener('gui-input-error', (event) => { cabin.errors = [event.detail.message]; }); cabin.addEventListener('gui-input', () => { cabin.errors = []; });</script>Change the messages with disabled-date-range-message, min-date-message, max-date-message,
invalid-date-message and incomplete-message, or translate them for every element, see
i18n.
Two months
Section titled “Two months”number-of-months shows several months side by side, which helps with long ranges.
import { GuiRangeDatePicker } from '@golemui/gui-components/react';import '@golemui/gui-components/index.css';
export function RangeDatePickerMonths() { return <GuiRangeDatePicker label="Summer rental" numberOfMonths={2} />;}import { Component, CUSTOM_ELEMENTS_SCHEMA } from '@angular/core';import '@golemui/gui-components/range-date-picker';
@Component({ selector: 'app-range-date-picker-months', schemas: [CUSTOM_ELEMENTS_SCHEMA], template: ` <gui-range-date-picker label="Summer rental" [numberOfMonths]="2" ></gui-range-date-picker> `,})export class RangeDatePickerMonthsComponent {}<script setup lang="ts">import '@golemui/gui-components/range-date-picker';import '@golemui/gui-components/index.css';</script>
<template> <gui-range-date-picker label="Summer rental" :number-of-months="2" ></gui-range-date-picker></template>import { LitElement, html } from 'lit';import { customElement } from 'lit/decorators.js';import '@golemui/gui-components/range-date-picker';import '@golemui/gui-components/index.css';
@customElement('range-date-picker-months')export class RangeDatePickerMonths extends LitElement { protected override createRenderRoot() { return this; }
override render() { return html` <gui-range-date-picker label="Summer rental" number-of-months="2" ></gui-range-date-picker> `; }}<gui-range-date-picker label="Summer rental" number-of-months="2"></gui-range-date-picker>Locale
Section titled “Locale”locale-id sets the language of the dates: the order of the parts, the format of the pills, the
names of the months and weekdays, and the first day of the week. Without it, the picker uses
English (en). See i18n.
import { GuiRangeDatePicker } from '@golemui/gui-components/react';import '@golemui/gui-components/index.css';
export function RangeDatePickerLocale() { return <GuiRangeDatePicker label="Fechas del viaje" localeId="es-ES" />;}import { Component, CUSTOM_ELEMENTS_SCHEMA } from '@angular/core';import '@golemui/gui-components/range-date-picker';
@Component({ selector: 'app-range-date-picker-locale', schemas: [CUSTOM_ELEMENTS_SCHEMA], template: ` <gui-range-date-picker label="Fechas del viaje" locale-id="es-ES" ></gui-range-date-picker> `,})export class RangeDatePickerLocaleComponent {}<script setup lang="ts">import '@golemui/gui-components/range-date-picker';import '@golemui/gui-components/index.css';</script>
<template> <gui-range-date-picker label="Fechas del viaje" locale-id="es-ES" ></gui-range-date-picker></template>import { LitElement, html } from 'lit';import { customElement } from 'lit/decorators.js';import '@golemui/gui-components/range-date-picker';import '@golemui/gui-components/index.css';
@customElement('range-date-picker-locale')export class RangeDatePickerLocale extends LitElement { protected override createRenderRoot() { return this; }
override render() { return html` <gui-range-date-picker label="Fechas del viaje" locale-id="es-ES" ></gui-range-date-picker> `; }}<gui-range-date-picker label="Fechas del viaje" locale-id="es-ES"></gui-range-date-picker>day-format, weekday-format and month-format change how the calendar writes the days, the
weekday names and the month.
Editing ranges
Section titled “Editing ranges”allow-edit lets the user change a range they already added. Select a pill, then press
F2 or E, or use its edit button: the range loads into the parts and the
calendar, and the pill shows the change as the user types. Enter or the confirm button saves it, and
Escape or the cancel button keeps the old range. Screen readers announce each step.
import { GuiRangeDatePicker } from '@golemui/gui-components/react';import type { DateRange } from '@golemui/gui-components';import '@golemui/gui-components/index.css';
const offsites: DateRange[] = [ { start: '2026-03-09', end: '2026-03-11' }, { start: '2026-06-15', end: '2026-06-17' },];
export function RangeDatePickerEdit() { return ( <GuiRangeDatePicker label="Team offsites" allowEdit value={offsites} /> );}import { Component, CUSTOM_ELEMENTS_SCHEMA } from '@angular/core';import type { DateRange } from '@golemui/gui-components';import '@golemui/gui-components/range-date-picker';
@Component({ selector: 'app-range-date-picker-edit', schemas: [CUSTOM_ELEMENTS_SCHEMA], template: ` <gui-range-date-picker label="Team offsites" allow-edit [value]="offsites" ></gui-range-date-picker> `,})export class RangeDatePickerEditComponent { offsites: DateRange[] = [ { start: '2026-03-09', end: '2026-03-11' }, { start: '2026-06-15', end: '2026-06-17' }, ];}<script setup lang="ts">import type { DateRange } from '@golemui/gui-components';import '@golemui/gui-components/range-date-picker';import '@golemui/gui-components/index.css';
const value: DateRange[] = [ { start: '2026-03-09', end: '2026-03-11' }, { start: '2026-06-15', end: '2026-06-17' },];</script>
<template> <gui-range-date-picker label="Team offsites" :allow-edit="true" :value.prop="value" ></gui-range-date-picker></template>import { LitElement, html } from 'lit';import { customElement } from 'lit/decorators.js';import type { DateRange } from '@golemui/gui-components';import '@golemui/gui-components/range-date-picker';import '@golemui/gui-components/index.css';
const offsites: DateRange[] = [ { start: '2026-03-09', end: '2026-03-11' }, { start: '2026-06-15', end: '2026-06-17' },];
@customElement('range-date-picker-edit')export class RangeDatePickerEdit extends LitElement { protected override createRenderRoot() { return this; }
override render() { return html` <gui-range-date-picker label="Team offsites" allow-edit .value=${offsites} ></gui-range-date-picker> `; }}<gui-range-date-picker label="Team offsites" allow-edit value='[{"start":"2026-03-09","end":"2026-03-11"},{"start":"2026-06-15","end":"2026-06-17"}]'></gui-range-date-picker>In a form
Section titled “In a form”With a name, <gui-range-date-picker> submits one entry per range under that name. Each entry
is the range as JSON, such as {"start":"2027-03-02","end":"2027-03-05"}, or
{"start":"2027-05-10"} for a single day. Read them all with FormData.getAll(). required
blocks the submit while the field has no range.
import { useState } from 'react';import { GuiButton, GuiRangeDatePicker } from '@golemui/gui-components/react';import '@golemui/gui-components/index.css';
export function RangeDatePickerNativeForm() { const [result, setResult] = useState('');
return ( <form id="rental-form" onSubmit={(event) => { event.preventDefault(); const ranges = new FormData(event.currentTarget).getAll('rental'); setResult(`Submitted ${ranges.join(', ')}`); }} > <div className="gui-grid"> <div className="gui-grid__cell"> <GuiRangeDatePicker label="Rental dates" name="rental" required /> </div> <div className="gui-grid__cell"> <GuiButton type="submit" label="Book" /> </div> </div> <p role="status">{result}</p> </form> );}import { Component, CUSTOM_ELEMENTS_SCHEMA, signal } from '@angular/core';import '@golemui/gui-components/button';import '@golemui/gui-components/range-date-picker';
@Component({ selector: 'app-range-date-picker-native-form', schemas: [CUSTOM_ELEMENTS_SCHEMA], template: ` <form id="rental-form" #form (submit)="onSubmit($event, form)"> <div class="gui-grid"> <div class="gui-grid__cell"> <gui-range-date-picker label="Rental dates" name="rental" required ></gui-range-date-picker> </div> <div class="gui-grid__cell"> <gui-button type="submit" label="Book"></gui-button> </div> </div> <p id="rental-result" role="status">{{ result() }}</p> </form> `,})export class RangeDatePickerNativeFormComponent { result = signal('');
onSubmit(event: Event, form: HTMLFormElement) { event.preventDefault(); const ranges = new FormData(form).getAll('rental'); this.result.set(`Submitted ${ranges.join(', ')}`); }}<script setup lang="ts">import { ref, useTemplateRef } from 'vue';import '@golemui/gui-components/button';import '@golemui/gui-components/range-date-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('rental'); result.value = `Submitted ${ranges.join(', ')}`;}</script>
<template> <form id="rental-form" ref="form" @submit.prevent="onSubmit"> <div class="gui-grid"> <div class="gui-grid__cell"> <gui-range-date-picker label="Rental dates" name="rental" :required="true" ></gui-range-date-picker> </div> <div class="gui-grid__cell"> <gui-button type="submit" label="Book"></gui-button> </div> </div> <p id="rental-result" role="status">{{ result }}</p> </form></template>import { LitElement, html } from 'lit';import { customElement, query, state } from 'lit/decorators.js';import '@golemui/gui-components/button';import '@golemui/gui-components/range-date-picker';import '@golemui/gui-components/index.css';
@customElement('range-date-picker-native-form')export class RangeDatePickerNativeForm 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('rental'); }
override render() { return html` <form id="rental-form" @submit=${this.onSubmit}> <div class="gui-grid"> <div class="gui-grid__cell"> <gui-range-date-picker label="Rental dates" name="rental" required ></gui-range-date-picker> </div> <div class="gui-grid__cell"> <gui-button type="submit" label="Book"></gui-button> </div> </div> <p role="status"> ${this.ranges && `Submitted ${this.ranges.join(', ')}`} </p> </form> `; }}<form id="rental-form"> <div class="gui-grid"> <div class="gui-grid__cell"> <gui-range-date-picker label="Rental dates" name="rental" required></gui-range-date-picker> </div> <div class="gui-grid__cell"> <gui-button type="submit" label="Book"></gui-button> </div> </div> <p id="rental-result" role="status"></p></form>
<script type="module"> const form = document.querySelector('#rental-form');
form.addEventListener('submit', (event) => { event.preventDefault(); const ranges = new FormData(form).getAll('rental'); document.querySelector('#rental-result').textContent = `Submitted ${ranges.join(', ')}`; });</script>A range that starts before min-date, ends after max-date or steps over a day of
disabled-ranges blocks the submit too, even one you set from code. The browser shows the same
message the picker gives the user, such as the disabled-date-range-message.
How the elements submit, validate and reset is in Native forms.
Styling
Section titled “Styling”Anatomy
Section titled “Anatomy”<gui-range-date-picker> renders into the light DOM, so you can style every part with plain CSS.
It renders a <gui-range-date> field, a toggle button and, while open, a panel with a
<gui-range-calendar>:
<gui-range-date-picker> <span class="gui-label" id="trip_label"> <span class="gui-label__text">Trip dates</span> </span> <div class="gui-widget"> <gui-range-date id="trip_date" class="gui-field"> <!-- The field: see the anatomy on the Date range page --> </gui-range-date> <button type="button" class="gui-range-date-picker__arrow" aria-haspopup="dialog" aria-label="Show calendar" aria-expanded="true" aria-controls="trip_popup" > <span class="gui-caret" aria-hidden="true"></span> </button> <div class="gui-picker__panel"> <gui-range-calendar role="dialog" id="trip_popup" aria-labelledby="trip_label" class="gui-field" > <!-- The calendar: see the anatomy on the Range calendar page --> </gui-range-calendar> </div> </div> <div class="gui-validator gui-validator--empty" id="trip_errors" role="alert" ></div></gui-range-date-picker>| Class | Part |
|---|---|
gui-label, gui-label__text | The label, and its text with the required marker. |
gui-widget | The box around the field, the button and the panel. |
gui-range-date-picker__arrow | The button that opens and closes the calendar. Its gui-caret turns while the calendar is open. |
gui-picker__panel | The popup that holds the calendar. While there are errors, it repeats them at its bottom. |
gui-validator | The errors container. gui-validator--empty is on it while there are no errors. |
The field inside is a <gui-range-date> and the
calendar a <gui-range-calendar>: their
classes style the picker too. The ids derive from uid, or from a generated id when you don’t set
one. Select by class or by tag, not by id.
Tokens
Section titled “Tokens”The field reads the tokens of the Date range field, and the calendar those of the Range calendar. The popup reads these:
| Token | What it styles |
|---|---|
--gui-bg-default | The background of the popup. |
--gui-border-default | The border of the popup. |
--gui-radius-md | The corner radius of the popup. |
--gui-shadow-md | The shadow of the popup. |
--gui-space-2 | The padding of the popup. |
--gui-intent-error | The popup’s border while invalid. |
--gui-text-default | The toggle button’s caret. |
/* Bigger days in every range picker. */gui-range-date-picker { --gui-calendar-day-button-size: 44px;}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 calendar popup. Without a label, the field has no accessible name and the popup is named “Calendar”. - Toggle button. The button that opens the calendar is named “Show calendar”; change it with
toggle-aria-label. It hasaria-haspopup="dialog", andaria-expandedtells whether the calendar is open. - Popup. The calendar is a dialog. Its days are a grid: each day is named by its full date,
such as “Monday, March 1, 2027”, today has
aria-current="date", the days in a range havearia-selected="true", and disabled days havearia-disabled="true". - Month changes. The calendar announces the month and year as the user moves to another month.
- Start, end and parts. As in the Date range field: the start and end are named “Start date” and “End date”, and each part by its type.
- Calendar buttons. The month buttons are named “Previous month” and “Next month”, and the year
button “Select year”. Change them with
prev-month-aria-label,next-month-aria-labelandselect-year-aria-label, and the year grid’s name withyear-grid-aria-label.
| Key | Action |
|---|---|
| Tab | Moves focus to the field’s first part, then to the toggle button, then into the open calendar. Leaving the picker closes it. |
| Focus on a part | Opens the calendar, and keeps the focus in the field so the user can type. |
| Enter Space | On the toggle button, opens the calendar and moves focus to it, or closes it. |
| ← → ↑ ↓ | In the calendar, move by a day or a week, into the next or previous month at the edges, skipping disabled days. |
| Page Up Page Down, in the calendar | Move to the same day in the previous or next month (the last day of a shorter month), or the nearest day the user can pick. With Shift, the same day in the previous or next year. |
| Home End, in the calendar | Move to the first or last day of the week the user can pick, within the month. |
| Enter Space | In the calendar, picks the focused day: the first pick starts a range, the second completes it. |
| Escape | Closes the calendar and moves focus back to the field. In the year grid, closes the grid first. |
The keys of the field and its pills are on the Date range page. What every element shares, such as translating the default names, is in Accessibility.