Date range
<gui-range-date> is a field to type date ranges. The user types a start and an end date part by
part, presses Enter, and the range becomes a pill. The field holds as many ranges as
the user adds.
import { GuiRangeDateInput } from '@golemui/gui-components/react';import '@golemui/gui-components/index.css';
export function RangeDateBasic() { return <GuiRangeDateInput label="Trip dates" />;}import { Component, CUSTOM_ELEMENTS_SCHEMA } from '@angular/core';import '@golemui/gui-components/range-date-input';
@Component({ selector: 'app-range-date-basic', schemas: [CUSTOM_ELEMENTS_SCHEMA], template: `<gui-range-date label="Trip dates"></gui-range-date>`,})export class RangeDateBasicComponent {}<script setup lang="ts">import '@golemui/gui-components/range-date-input';import '@golemui/gui-components/index.css';</script>
<template> <gui-range-date label="Trip dates"></gui-range-date></template>import { LitElement, html } from 'lit';import { customElement } from 'lit/decorators.js';import '@golemui/gui-components/range-date-input';import '@golemui/gui-components/index.css';
@customElement('range-date-basic')export class RangeDateBasic extends LitElement { protected override createRenderRoot() { return this; }
override render() { return html`<gui-range-date label="Trip dates"></gui-range-date>`; }}<gui-range-date label="Trip dates"></gui-range-date>To pick the days in a calendar instead, use the Date range picker. For a single date, use the Date input.
The element’s entry point is @golemui/gui-components/range-date-input, which registers
<gui-range-date> and the pills 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 { GuiRangeDate } from '@golemui/gui-components/react';import '@golemui/gui-components/index.css';
export function TripsField() { const [trips, setTrips] = useState([]);
return ( <GuiRangeDate 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, GuiRangeDateEvents } from '@golemui/gui-components';import '@golemui/gui-components/range-date-input';
type ChangeEvent = GuiEventMap<typeof GuiRangeDateEvents>['gui-change'];
@Component({ selector: 'app-trips-field', schemas: [CUSTOM_ELEMENTS_SCHEMA], template: ` <gui-range-date label="Trip dates" name="trips" [value]="trips" (gui-change)="onChange($event)" ></gui-range-date> `,})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-input';import '@golemui/gui-components/index.css';
const trips = ref([]);</script>
<template> <gui-range-date 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-input';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 label="Trip dates" name="trips" .value=${this.trips} @gui-change=${(event: CustomEvent) => (this.trips = event.detail.value)} ></gui-range-date> `; }}With any bundler, such as Vite. Import the element once, anywhere, and use the tag in your markup.
<gui-range-date label="Trip dates" name="trips"></gui-range-date>
<script type="module" src="./main.js"></script>import '@golemui/gui-components/range-date-input';import '@golemui/gui-components/index.css';
const element = document.querySelector('gui-range-date');
element.addEventListener('gui-change', (event) => { console.log(event.detail.value);});Properties
| Property | Type | Default | Description |
|---|---|---|---|
localeId
attribute locale-id | string | – | BCP 47 locale for formatting and parsing, such as en-US or es. |
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. |
showHint
attribute show-hint | boolean | true | Whether the element renders its hint. Elements that embed it turn it off and show the hint themselves: aria-describedby still points at the hint by its id. |
icon | string | '' | Icon class name shown inside the control, for example from an icon font. |
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. |
value | DateRange[] | [] | The date ranges, as { start, end } ISO dates. |
invalidDateMessage
attribute invalid-date-message | string | – | Error for a complete but impossible date, such as February 31. |
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. |
separator | string | – | Text shown between the start and end of a range. |
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-focus | One of the parts of the field received focus. |
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-parts-change | The typed parts changed before they form a complete value, for a host that mirrors them. |
gui-edit-state-change | Editing a range in place started, changed selection or ended. detail has the selected range and whether it is editing. |
gui-range-click | The user clicked a range pill. detail.range is the range. |
gui-dropdown-toggle | The count of ranges opened or closed its dropdown. detail.open is the new state. |
CSS custom properties
| Property | Default | Description |
|---|---|---|
--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”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.
import { GuiRangeDateInput } from '@golemui/gui-components/react';import type { DateRange } from '@golemui/gui-components';import '@golemui/gui-components/index.css';
const closures: DateRange[] = [ { start: '2026-12-24', end: '2026-12-26' }, { start: '2026-12-31' },];
export function RangeDateValue() { return <GuiRangeDateInput label="Office closures" value={closures} />;}import { Component, CUSTOM_ELEMENTS_SCHEMA } from '@angular/core';import type { DateRange } from '@golemui/gui-components';import '@golemui/gui-components/range-date-input';
@Component({ selector: 'app-range-date-value', schemas: [CUSTOM_ELEMENTS_SCHEMA], template: ` <gui-range-date label="Office closures" [value]="closures"></gui-range-date> `,})export class RangeDateValueComponent { closures: DateRange[] = [ { start: '2026-12-24', end: '2026-12-26' }, { start: '2026-12-31' }, ];}<script setup lang="ts">import type { DateRange } from '@golemui/gui-components';import '@golemui/gui-components/range-date-input';import '@golemui/gui-components/index.css';
const value: DateRange[] = [ { start: '2026-12-24', end: '2026-12-26' }, { start: '2026-12-31' },];</script>
<template> <gui-range-date label="Office closures" :value.prop="value"></gui-range-date></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-input';import '@golemui/gui-components/index.css';
const closures: DateRange[] = [ { start: '2026-12-24', end: '2026-12-26' }, { start: '2026-12-31' },];
@customElement('range-date-value')export class RangeDateValue extends LitElement { protected override createRenderRoot() { return this; }
override render() { return html` <gui-range-date label="Office closures" .value=${closures} ></gui-range-date> `; }}<gui-range-date label="Office closures" value='[{"start":"2026-12-24","end":"2026-12-26"},{"start":"2026-12-31"}]'></gui-range-date>The field keeps the list tidy as the user adds ranges:
- An end typed before its start is swapped, so
10/09/2026 - 10/05/2026becomes October 5 to 9. - A range that overlaps or touches another one merges with it: October 5 to 9 and October 10 to 12 become October 5 to 12.
- 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.
Locale
Section titled “Locale”locale-id sets the language of the dates: the order of the parts, such as day, month and year in
Spanish, and the format of the pills. See i18n.
import { GuiRangeDateInput } from '@golemui/gui-components/react';import '@golemui/gui-components/index.css';
export function RangeDateLocale() { return <GuiRangeDateInput label="Fechas del viaje" localeId="es-ES" />;}import { Component, CUSTOM_ELEMENTS_SCHEMA } from '@angular/core';import '@golemui/gui-components/range-date-input';
@Component({ selector: 'app-range-date-locale', schemas: [CUSTOM_ELEMENTS_SCHEMA], template: ` <gui-range-date label="Fechas del viaje" locale-id="es-ES"></gui-range-date> `,})export class RangeDateLocaleComponent {}<script setup lang="ts">import '@golemui/gui-components/range-date-input';import '@golemui/gui-components/index.css';</script>
<template> <gui-range-date label="Fechas del viaje" locale-id="es-ES"></gui-range-date></template>import { LitElement, html } from 'lit';import { customElement } from 'lit/decorators.js';import '@golemui/gui-components/range-date-input';import '@golemui/gui-components/index.css';
@customElement('range-date-locale')export class RangeDateLocale extends LitElement { protected override createRenderRoot() { return this; }
override render() { return html` <gui-range-date label="Fechas del viaje" locale-id="es-ES" ></gui-range-date> `; }}<gui-range-date label="Fechas del viaje" locale-id="es-ES"></gui-range-date>Editing ranges
Section titled “Editing ranges”allow-edit lets the user change a range they already added. Select a pill, then press
F2 or E, or use its edit button: the range loads into the parts and the pill
shows the change as the user types. Enter or the confirm button saves it, and
Escape or the cancel button keeps the old range. Screen readers announce each step.
import { GuiRangeDateInput } 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 RangeDateEdit() { return <GuiRangeDateInput 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-input';
@Component({ selector: 'app-range-date-edit', schemas: [CUSTOM_ELEMENTS_SCHEMA], template: ` <gui-range-date label="Team offsites" allow-edit [value]="offsites" ></gui-range-date> `,})export class RangeDateEditComponent { 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-input';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 label="Team offsites" :allow-edit="true" :value.prop="value" ></gui-range-date></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-input';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-edit')export class RangeDateEdit extends LitElement { protected override createRenderRoot() { return this; }
override render() { return html` <gui-range-date label="Team offsites" allow-edit .value=${offsites} ></gui-range-date> `; }}<gui-range-date 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>Disabled and read-only
Section titled “Disabled and read-only”A disabled field can’t be focused or changed, and a form doesn’t submit it. A readonly field
shows its ranges but doesn’t let the user add or remove one, and a form submits its value.
import { GuiRangeDateInput } from '@golemui/gui-components/react';import type { DateRange } from '@golemui/gui-components';import '@golemui/gui-components/index.css';
const august: DateRange[] = [{ start: '2026-08-01', end: '2026-08-15' }];
export function RangeDateDisabledReadonly() { return ( <div className="gui-grid"> <div className="gui-grid__cell"> <GuiRangeDateInput label="Blackout dates" value={august} disabled /> </div> <div className="gui-grid__cell"> <GuiRangeDateInput label="Booked stays" value={august} readOnly /> </div> </div> );}import { Component, CUSTOM_ELEMENTS_SCHEMA } from '@angular/core';import type { DateRange } from '@golemui/gui-components';import '@golemui/gui-components/range-date-input';
@Component({ selector: 'app-range-date-disabled-readonly', schemas: [CUSTOM_ELEMENTS_SCHEMA], template: ` <div class="gui-grid"> <div class="gui-grid__cell"> <gui-range-date label="Blackout dates" [value]="august" disabled ></gui-range-date> </div> <div class="gui-grid__cell"> <gui-range-date label="Booked stays" [value]="august" readonly ></gui-range-date> </div> </div> `,})export class RangeDateDisabledReadonlyComponent { august: DateRange[] = [{ start: '2026-08-01', end: '2026-08-15' }];}<script setup lang="ts">import type { DateRange } from '@golemui/gui-components';import '@golemui/gui-components/range-date-input';import '@golemui/gui-components/index.css';
const value: DateRange[] = [{ start: '2026-08-01', end: '2026-08-15' }];</script>
<template> <div class="gui-grid"> <div class="gui-grid__cell"> <gui-range-date label="Blackout dates" :value.prop="value" :disabled="true" ></gui-range-date> </div> <div class="gui-grid__cell"> <gui-range-date label="Booked stays" :value.prop="value" :readOnly="true" ></gui-range-date> </div> </div></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-input';import '@golemui/gui-components/index.css';
const august: DateRange[] = [{ start: '2026-08-01', end: '2026-08-15' }];
@customElement('range-date-disabled-readonly')export class RangeDateDisabledReadonly extends LitElement { protected override createRenderRoot() { return this; }
override render() { return html` <div class="gui-grid"> <div class="gui-grid__cell"> <gui-range-date label="Blackout dates" .value=${august} disabled ></gui-range-date> </div> <div class="gui-grid__cell"> <gui-range-date label="Booked stays" .value=${august} readonly ></gui-range-date> </div> </div> `; }}<div class="gui-grid"> <div class="gui-grid__cell"> <gui-range-date label="Blackout dates" value='[{"start":"2026-08-01","end":"2026-08-15"}]' disabled ></gui-range-date> </div> <div class="gui-grid__cell"> <gui-range-date label="Booked stays" value='[{"start":"2026-08-01","end":"2026-08-15"}]' readonly ></gui-range-date> </div></div>Errors
Section titled “Errors”The field checks each date as the user enters it. When it rejects one, such as February 31, or
when focus leaves a range typed halfway, 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 once the problem is fixed.
import { useState } from 'react';import { GuiRangeDateInput } from '@golemui/gui-components/react';import '@golemui/gui-components/index.css';
export function RangeDateErrors() { const [errors, setErrors] = useState<string[]>([]);
return ( <GuiRangeDateInput id="leave-dates" label="Leave dates" hint="Type a date that doesn't exist, such as 02/31/2026, then leave the field." errors={errors} onGuiInputError={(event) => setErrors([event.detail.message])} onGuiInput={() => setErrors([])} /> );}import { Component, CUSTOM_ELEMENTS_SCHEMA, signal } from '@angular/core';import type { GuiEventMap, GuiRangeDateInputEvents,} from '@golemui/gui-components';import '@golemui/gui-components/range-date-input';
type RangeDateEvent<K extends keyof typeof GuiRangeDateInputEvents> = GuiEventMap<typeof GuiRangeDateInputEvents>[K];
@Component({ selector: 'app-range-date-errors', schemas: [CUSTOM_ELEMENTS_SCHEMA], template: ` <gui-range-date id="leave-dates" label="Leave dates" hint="Type a date that doesn't exist, such as 02/31/2026, then leave the field." [errors]="errors()" (gui-input-error)="onInputError($event)" (gui-input)="errors.set([])" ></gui-range-date> `,})export class RangeDateErrorsComponent { errors = signal<string[]>([]);
onInputError(event: Event) { this.errors.set([ (event as RangeDateEvent<'gui-input-error'>).detail.message, ]); }}<script setup lang="ts">import { ref } from 'vue';import '@golemui/gui-components/range-date-input';import '@golemui/gui-components/index.css';
const errors = ref<string[]>([]);</script>
<template> <gui-range-date id="leave-dates" label="Leave dates" hint="Type a date that doesn't exist, such as 02/31/2026, then leave the field." :errors.prop="errors" @gui-input-error="errors = [$event.detail.message]" @gui-input="errors = []" ></gui-range-date></template>import { LitElement, html } from 'lit';import { customElement, state } from 'lit/decorators.js';import type { GuiEventMap, GuiRangeDateInputEvents,} from '@golemui/gui-components';import '@golemui/gui-components/range-date-input';import '@golemui/gui-components/index.css';
type RangeDateEvent<K extends keyof typeof GuiRangeDateInputEvents> = GuiEventMap<typeof GuiRangeDateInputEvents>[K];
@customElement('range-date-errors')export class RangeDateErrors extends LitElement { @state() private errors: string[] = [];
protected override createRenderRoot() { return this; }
private onInputError(event: RangeDateEvent<'gui-input-error'>) { this.errors = [event.detail.message]; }
private onInput() { this.errors = []; }
override render() { return html` <gui-range-date id="leave-dates" label="Leave dates" hint="Type a date that doesn't exist, such as 02/31/2026, then leave the field." .errors=${this.errors} @gui-input-error=${this.onInputError} @gui-input=${this.onInput} ></gui-range-date> `; }}<gui-range-date id="leave-dates" label="Leave dates" hint="Type a date that doesn't exist, such as 02/31/2026, then leave the field."></gui-range-date>
<script type="module"> const leave = document.querySelector('#leave-dates');
leave.addEventListener('gui-input-error', (event) => { leave.errors = [event.detail.message]; }); leave.addEventListener('gui-input', () => { leave.errors = []; });</script>Your own validation works the same way: set errors to a list of messages. See
Errors.
In a form
Section titled “In a form”With a name, <gui-range-date> submits one entry per range under that name. Each entry is the
range as JSON, such as {"start":"2026-10-05","end":"2026-10-09"}, or {"start":"2026-12-28"}
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, GuiRangeDateInput } from '@golemui/gui-components/react';import '@golemui/gui-components/index.css';
export function RangeDateNativeForm() { const [result, setResult] = useState('');
return ( <form id="vacation-form" onSubmit={(event) => { event.preventDefault(); const ranges = new FormData(event.currentTarget).getAll('vacation'); setResult(`Submitted ${ranges.join(', ')}`); }} > <div className="gui-grid"> <div className="gui-grid__cell"> <GuiRangeDateInput label="Vacation" name="vacation" required /> </div> <div className="gui-grid__cell"> <GuiButton type="submit" label="Request" /> </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-input';
@Component({ selector: 'app-range-date-native-form', schemas: [CUSTOM_ELEMENTS_SCHEMA], template: ` <form id="vacation-form" #form (submit)="onSubmit($event, form)"> <div class="gui-grid"> <div class="gui-grid__cell"> <gui-range-date label="Vacation" name="vacation" required ></gui-range-date> </div> <div class="gui-grid__cell"> <gui-button type="submit" label="Request"></gui-button> </div> </div> <p id="vacation-result" role="status">{{ result() }}</p> </form> `,})export class RangeDateNativeFormComponent { result = signal('');
onSubmit(event: Event, form: HTMLFormElement) { event.preventDefault(); const ranges = new FormData(form).getAll('vacation'); 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-input';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('vacation'); result.value = `Submitted ${ranges.join(', ')}`;}</script>
<template> <form id="vacation-form" ref="form" @submit.prevent="onSubmit"> <div class="gui-grid"> <div class="gui-grid__cell"> <gui-range-date label="Vacation" name="vacation" :required="true" ></gui-range-date> </div> <div class="gui-grid__cell"> <gui-button type="submit" label="Request"></gui-button> </div> </div> <p id="vacation-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-input';import '@golemui/gui-components/index.css';
@customElement('range-date-native-form')export class RangeDateNativeForm 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('vacation'); }
override render() { return html` <form id="vacation-form" @submit=${this.onSubmit}> <div class="gui-grid"> <div class="gui-grid__cell"> <gui-range-date label="Vacation" name="vacation" required ></gui-range-date> </div> <div class="gui-grid__cell"> <gui-button type="submit" label="Request"></gui-button> </div> </div> <p role="status"> ${this.ranges && `Submitted ${this.ranges.join(', ')}`} </p> </form> `; }}<form id="vacation-form"> <div class="gui-grid"> <div class="gui-grid__cell"> <gui-range-date label="Vacation" name="vacation" required></gui-range-date> </div> <div class="gui-grid__cell"> <gui-button type="submit" label="Request"></gui-button> </div> </div> <p id="vacation-result" role="status"></p></form>
<script type="module"> const form = document.querySelector('#vacation-form');
form.addEventListener('submit', (event) => { event.preventDefault(); const ranges = new FormData(form).getAll('vacation'); document.querySelector('#vacation-result').textContent = `Submitted ${ranges.join(', ')}`; });</script>How the elements submit, validate and reset is in Native forms.
Styling
Section titled “Styling”Anatomy
Section titled “Anatomy”<gui-range-date> renders into the light DOM, so you can style every part with plain CSS. These
are the parts and their public classes, with one range, a hint, an icon and an error:
<gui-range-date class="gui-field"> <span class="gui-label" id="trip_label"> <span class="gui-label__text"> Trip dates<span aria-hidden="true"> *</span> </span> <div class="gui-widget-hint" id="trip_hint" aria-hidden="true"> Add every leg of the trip. </div> </span> <div class="gui-widget"> <div role="group" class="gui-widget-input gui-parts-ring gui-range-date-input gui-range-date-input--icon" aria-labelledby="trip_label" aria-describedby="trip_hint" aria-invalid="true" aria-errormessage="trip_errors" > <span aria-hidden="true" class="gui-widget-icon icon-calendar" data-icon="icon-calendar" ></span> <gui-pills class="gui-range-date-input__pills"> <div class="gui-pills__strip-wrapper"> <div class="gui-pills__strip" role="toolbar" aria-label="Selected date ranges" > <button type="button" class="gui-pills__pill gui-pills__pill--clickable" aria-label="10/05/2026 - 10/09/2026" aria-description="Remove date" > <span class="gui-pills__pill-text">10/05/2026 - 10/09/2026</span> <span class="gui-pills__pill-remove" aria-hidden="true"> <svg>…</svg> </span> </button> </div> </div> <div class="gui-pills__compact"> <button type="button" class="gui-pills__count" aria-haspopup="true" aria-label="1 date range" > 1 </button> </div> </gui-pills> <div class="gui-range-date-input__inputs"> <div class="gui-parts gui-range-date-input__field" role="group" aria-label="Start date" > <div class="gui-parts__touch-target gui-range-date-input__touch-target" > <input type="text" inputmode="numeric" role="spinbutton" class="gui-parts__part gui-range-date-input__part" placeholder="mm" aria-label="Month" aria-required="true" /> <div class="gui-parts__visual-underline gui-range-date-input__visual-underline" ></div> </div> <span class="gui-range-date-input__separator">/</span> <!-- The day, then the year, with gui-parts__year gui-range-date-input__year --> </div> <span class="gui-range-date-input__separator" aria-hidden="true"> - </span> <div class="gui-parts gui-range-date-input__field" role="group" aria-label="End date" > <!-- The same parts as the start --> </div> </div> </div> </div> <div class="gui-validator" id="trip_errors" role="alert"> <div class="gui-validator__error">Add the return trip.</div> </div></gui-range-date>| Class | Part |
|---|---|
gui-field | The element itself. Lines its parts up with the fields next to it in a gui-grid row. |
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 control. |
gui-widget-input, gui-parts-ring | The part that looks like a field. gui-parts-ring draws its focus ring and its error border. |
gui-range-date-input | The field of this element, with the pills and the parts. |
gui-range-date-input--icon | On the field when there is an icon: it makes room for it. |
gui-widget-icon | The icon, with your icon classes. |
gui-range-date-input__pills | The pills, a <gui-pills> element. |
gui-pills__pill | One range. gui-pills__pill-text is its label and gui-pills__pill-remove its remove icon. |
gui-pills__count | The count of ranges that replaces the pills when the field is narrow. It opens them in a dropdown. |
gui-range-date-input__inputs | The start and the end. |
gui-range-date-input__field | The start, or the end: a gui-parts group of parts. |
gui-range-date-input__part | One part, such as the month. gui-parts__part is the class every segmented field shares. |
gui-range-date-input__year | The year part, which is wider. |
gui-range-date-input__separator | The text between the parts, and between the start and the end (separator, - by default). |
gui-range-date-input__visual-underline | The line under the focused part. |
gui-validator, gui-validator__error | The errors container, when there are errors, and one error message. |
The pills switch to the count when the field is narrower than 540px. The ids derive from uid, or
from a generated id such as gui-range-date-3 when you don’t set one. Select by class or by tag,
not by id.
Tokens
Section titled “Tokens”The field reads these design tokens. Set them on :root to change every field, or on any element
to change the fields inside it:
| Token | What it styles |
|---|---|
--gui-widget-size | The height of the field. |
--gui-radius-md | The corner radius of the field and the pills. |
--gui-bg-surface | The field’s background. |
--gui-border-default | The border, and the lines after the icon and the pills. |
--gui-text-default | The parts, the label and the icon. |
--gui-font-family | The font of the pills. The parts use a monospace font. |
--gui-font-sm | The text size of the label and the field. |
--gui-font-xs | The text size of the hint, the errors and the count. |
--gui-text-secondary | The hint. |
--gui-border-focus | The border while a part or a pill has focus. |
--gui-shadow-focus | The focus ring of the field and of the pills. |
--gui-intent-error | The border and the messages when there are errors. |
--gui-shadow-focus-error | The focus ring when there are errors. |
--gui-bg-disabled | The background when disabled. |
--gui-text-disabled | The text when disabled. |
--gui-intent-primary | The background of the pills and the count. |
--gui-color-white | The text of the pills and the count. |
--gui-pill-height | The height of each pill. |
--gui-pill-font-size | The text size of the pills. |
--gui-pill-action-size, --gui-pill-action-hit | The size of the icons in a pill, and of their clickable area. |
--gui-bg-default, --gui-shadow-md | The background and the shadow of the dropdown the count opens. |
--gui-space-1, --gui-space-2 | The space between the label, hint and errors, and between pills and parts. |
/* Taller pills with larger text. */gui-range-date { --gui-pill-height: 28px; --gui-pill-font-size: var(--gui-font-sm);}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, a group with the start and the end. Without a label the field has no accessible name, and development builds log a warning. - Start and end. The start and the end are groups named “Start date” and “End date”. Change
the names with
start-date-aria-labelandend-date-aria-label; an empty value removes them. - Parts. Each part is a spin button named “Day”, “Month” or “Year”, with its minimum, maximum
and current value. Change the names with
day-aria-label,month-aria-labelandyear-aria-label. - Pills. The pills are a toolbar named “Selected date ranges”. Each pill is a button named by
its range, described as “Remove date”: change that with
remove-pill-aria-label. When the field is narrow, the count button is named after the number of ranges, such as “2 date ranges”. - Description and errors. The
hintis the field’s description. While errors show, the field hasaria-invalid="true"and points at them witharia-errormessage, and screen readers announce new errors as they appear. - Required.
requiredsetsaria-requiredon every part; the visual marker is hidden from screen readers. - Editing. With
allow-edit, screen readers announce when editing starts, is saved or is cancelled. Change the announcements withedit-started-message,edit-committed-messageandedit-cancelled-message.
In the parts:
| Key | Action |
|---|---|
| Tab | Moves focus to the first part of the start, and away from the field. The other parts are not tab stops. |
| Digits | Fill the part. A full part moves on to the next empty part, from the start to the end. |
| ← → | Move between the parts, and between the start and the end. ← on the first part moves to the last pill. |
| ↑ ↓ | Increase or decrease the part. |
| Backspace Delete | Clear the part. On an empty part, while every part is empty, they move to the last pill. |
| Enter | Adds the range typed, or saves the range being edited. |
On a pill:
| Key | Action |
|---|---|
| ← → | Move to the previous or next pill. → on the last pill moves back to the parts. |
| Home End | Move to the first or last pill. |
| Delete Backspace | Remove the range. |
| F2 E | With allow-edit, edit the range. |
| Escape | With allow-edit, cancels the edit, then clears the selected pill. |
What every element shares, such as translating the default names, is in Accessibility.