Date input
<gui-date> is a date field typed part by part: the day, the month and the year each have their
own segment, in the order of the locale. Its value is an ISO date, such as 2026-10-05. To pick
the date from a calendar too, use the Date picker.
import { GuiDate } from '@golemui/gui-components/react';import '@golemui/gui-components/index.css';
export function DateBasic() { return <GuiDate label="Date of birth" />;}import { Component, CUSTOM_ELEMENTS_SCHEMA } from '@angular/core';import '@golemui/gui-components/date-input';
@Component({ selector: 'app-date-basic', schemas: [CUSTOM_ELEMENTS_SCHEMA], template: `<gui-date label="Date of birth"></gui-date>`,})export class DateBasicComponent {}<script setup lang="ts">import '@golemui/gui-components/date-input';import '@golemui/gui-components/index.css';</script>
<template> <gui-date label="Date of birth"></gui-date></template>import { LitElement, html } from 'lit';import { customElement } from 'lit/decorators.js';import '@golemui/gui-components/date-input';import '@golemui/gui-components/index.css';
@customElement('date-basic')export class DateBasic extends LitElement { protected override createRenderRoot() { return this; }
override render() { return html`<gui-date label="Date of birth"></gui-date>`; }}<gui-date label="Date of birth"></gui-date>The element’s entry point is @golemui/gui-components/date-input, which registers <gui-date>
and nothing else. 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 { GuiDate } from '@golemui/gui-components/react';import '@golemui/gui-components/index.css';
export function BirthDateField() { const [birthDate, setBirthDate] = useState('');
return ( <GuiDate label="Date of birth" name="birthDate" value={birthDate} onGuiChange={(event) => setBirthDate(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, GuiDateEvents } from '@golemui/gui-components';import '@golemui/gui-components/date-input';
type ChangeEvent = GuiEventMap<typeof GuiDateEvents>['gui-change'];
@Component({ selector: 'app-birth-date-field', schemas: [CUSTOM_ELEMENTS_SCHEMA], template: ` <gui-date label="Date of birth" name="birthDate" [value]="birthDate" (gui-change)="onChange($event)" ></gui-date> `,})export class BirthDateFieldComponent { birthDate = '';
onChange(event: Event) { this.birthDate = (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/date-input';import '@golemui/gui-components/index.css';
const birthDate = ref('');</script>
<template> <gui-date label="Date of birth" name="birthDate" :value="birthDate" @gui-change="birthDate = $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/date-input';import '@golemui/gui-components/index.css';
@customElement('birth-date-field')export class BirthDateField extends LitElement { @state() private birthDate = '';
protected override createRenderRoot() { return this; }
override render() { return html` <gui-date label="Date of birth" name="birthDate" .value=${this.birthDate} @gui-change=${(event: CustomEvent) => (this.birthDate = event.detail.value)} ></gui-date> `; }}With any bundler, such as Vite. Import the element once, anywhere, and use the tag in your markup.
<gui-date label="Date of birth" name="birthDate"></gui-date>
<script type="module" src="./main.js"></script>import '@golemui/gui-components/date-input';import '@golemui/gui-components/index.css';
const element = document.querySelector('gui-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 | string | – | The date, as an ISO date (YYYY-MM-DD). |
invalidDateMessage
attribute invalid-date-message | string | – | Error for a complete but impossible date, such as February 31. |
incompleteMessage
attribute incomplete-message | string | – | Error when focus leaves a partly filled value. |
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). |
minDateMessage
attribute min-date-message | string | – | Error for a date before minDate. |
maxDateMessage
attribute max-date-message | string | – | Error for a date after maxDate. |
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 committed the value. detail.value is the committed value. |
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. |
Examples
Section titled “Examples”Locale
Section titled “Locale”locale-id takes a BCP 47 locale, such as en-US or de-DE. It sets the order of the parts and
the separators between them. Without it, the field uses the English order: month, day, year. The
value is the same ISO date in every locale.
import { GuiDate } from '@golemui/gui-components/react';import '@golemui/gui-components/index.css';
export function DateLocale() { return ( <div className="gui-grid gui-grid--row"> <div className="gui-grid__cell"> <GuiDate label="United States" localeId="en-US" value="2026-07-04" /> </div> <div className="gui-grid__cell"> <GuiDate label="Germany" localeId="de-DE" value="2026-07-04" /> </div> <div className="gui-grid__cell"> <GuiDate label="Japan" localeId="ja-JP" value="2026-07-04" /> </div> </div> );}import { Component, CUSTOM_ELEMENTS_SCHEMA } from '@angular/core';import '@golemui/gui-components/date-input';
@Component({ selector: 'app-date-locale', schemas: [CUSTOM_ELEMENTS_SCHEMA], template: ` <div class="gui-grid gui-grid--row"> <div class="gui-grid__cell"> <gui-date label="United States" locale-id="en-US" value="2026-07-04" ></gui-date> </div> <div class="gui-grid__cell"> <gui-date label="Germany" locale-id="de-DE" value="2026-07-04" ></gui-date> </div> <div class="gui-grid__cell"> <gui-date label="Japan" locale-id="ja-JP" value="2026-07-04"></gui-date> </div> </div> `,})export class DateLocaleComponent {}<script setup lang="ts">import '@golemui/gui-components/date-input';import '@golemui/gui-components/index.css';</script>
<template> <div class="gui-grid gui-grid--row"> <div class="gui-grid__cell"> <gui-date label="United States" locale-id="en-US" value="2026-07-04" ></gui-date> </div> <div class="gui-grid__cell"> <gui-date label="Germany" locale-id="de-DE" value="2026-07-04"></gui-date> </div> <div class="gui-grid__cell"> <gui-date label="Japan" locale-id="ja-JP" value="2026-07-04"></gui-date> </div> </div></template>import { LitElement, html } from 'lit';import { customElement } from 'lit/decorators.js';import '@golemui/gui-components/date-input';import '@golemui/gui-components/index.css';
@customElement('date-locale')export class DateLocale extends LitElement { protected override createRenderRoot() { return this; }
override render() { return html` <div class="gui-grid gui-grid--row"> <div class="gui-grid__cell"> <gui-date label="United States" locale-id="en-US" value="2026-07-04" ></gui-date> </div> <div class="gui-grid__cell"> <gui-date label="Germany" locale-id="de-DE" value="2026-07-04" ></gui-date> </div> <div class="gui-grid__cell"> <gui-date label="Japan" locale-id="ja-JP" value="2026-07-04" ></gui-date> </div> </div> `; }}<div class="gui-grid gui-grid--row"> <div class="gui-grid__cell"> <gui-date label="United States" locale-id="en-US" value="2026-07-04"></gui-date> </div> <div class="gui-grid__cell"> <gui-date label="Germany" locale-id="de-DE" value="2026-07-04"></gui-date> </div> <div class="gui-grid__cell"> <gui-date label="Japan" locale-id="ja-JP" value="2026-07-04"></gui-date> </div></div>Value and events
Section titled “Value and events”value takes an ISO date (YYYY-MM-DD) and holds the date the user enters. The element fires
gui-input and then gui-change as soon as all three parts form a real date, with the ISO date
in event.detail.value. When the user clears a part, the value becomes empty and both events fire
with null.
Value: 2026-11-30
import { useState } from 'react';import { GuiDate } from '@golemui/gui-components/react';import '@golemui/gui-components/index.css';
export function DateValue() { const [value, setValue] = useState<string | undefined>('2026-11-30');
return ( <> <GuiDate id="date-renewal" label="Renewal date" value={value} onGuiChange={(event) => setValue(event.detail.value)} /> <p role="status">Value: {value}</p> </> );}import { Component, CUSTOM_ELEMENTS_SCHEMA, signal } from '@angular/core';import type { GuiDateEvents, GuiEventMap } from '@golemui/gui-components';import '@golemui/gui-components/date-input';
type DateEvent<K extends keyof typeof GuiDateEvents> = GuiEventMap< typeof GuiDateEvents>[K];
@Component({ selector: 'app-date-value', schemas: [CUSTOM_ELEMENTS_SCHEMA], template: ` <gui-date id="date-renewal" label="Renewal date" [value]="value()" (gui-change)="onChange($event)" ></gui-date> <p id="date-renewal-value" role="status">Value: {{ value() }}</p> `,})export class DateValueComponent { value = signal<string | undefined>('2026-11-30');
onChange(event: Event) { this.value.set((event as DateEvent<'gui-change'>).detail.value); }}<script setup lang="ts">import { ref } from 'vue';import '@golemui/gui-components/date-input';import '@golemui/gui-components/index.css';
const value = ref<string | undefined>('2026-11-30');</script>
<template> <gui-date id="date-renewal" label="Renewal date" :value="value" @gui-change="value = $event.detail.value" ></gui-date> <p id="date-renewal-value" role="status">Value: {{ value }}</p></template>import { LitElement, html } from 'lit';import { customElement, state } from 'lit/decorators.js';import type { GuiDateEvents, GuiEventMap } from '@golemui/gui-components';import '@golemui/gui-components/date-input';import '@golemui/gui-components/index.css';
type DateEvent<K extends keyof typeof GuiDateEvents> = GuiEventMap< typeof GuiDateEvents>[K];
@customElement('date-value')export class DateValue extends LitElement { @state() private value: string | undefined = '2026-11-30';
protected override createRenderRoot() { return this; }
private onChange(event: DateEvent<'gui-change'>) { this.value = event.detail.value; }
override render() { return html` <gui-date id="date-renewal" label="Renewal date" .value=${this.value} @gui-change=${this.onChange} ></gui-date> <p role="status">Value: ${this.value}</p> `; }}<gui-date id="date-renewal" label="Renewal date" value="2026-11-30"></gui-date><p id="date-renewal-value" role="status">Value: 2026-11-30</p>
<script type="module"> const renewal = document.querySelector('#date-renewal');
renewal.addEventListener('gui-change', (event) => { document.querySelector('#date-renewal-value').textContent = `Value: ${event.detail.value}`; });</script>Bounds
Section titled “Bounds”min-date and max-date take ISO dates and are inclusive. A date outside them is still entered:
the element keeps it as its value, marks it as out of bounds for its form and fires
gui-input-error with the reason.
The element doesn’t show these messages by itself. Show them with errors, as below: the
gui-input-error listener shows the message, and the next gui-input clears it.
import { useState } from 'react';import { GuiDate } from '@golemui/gui-components/react';import '@golemui/gui-components/index.css';
export function DateBounds() { const [errors, setErrors] = useState<string[]>([]);
return ( <GuiDate id="date-start" label="Start date" hint="Any day in 2027." minDate="2027-01-01" maxDate="2027-12-31" errors={errors} // The element reports what it rejects; show it with errors. onGuiInputError={(event) => setErrors([event.detail.message])} onGuiInput={() => setErrors([])} /> );}import { Component, CUSTOM_ELEMENTS_SCHEMA, signal } from '@angular/core';import type { GuiDateEvents, GuiEventMap } from '@golemui/gui-components';import '@golemui/gui-components/date-input';
type DateEvent<K extends keyof typeof GuiDateEvents> = GuiEventMap< typeof GuiDateEvents>[K];
@Component({ selector: 'app-date-bounds', schemas: [CUSTOM_ELEMENTS_SCHEMA], template: ` <gui-date id="date-start" label="Start date" hint="Any day in 2027." min-date="2027-01-01" max-date="2027-12-31" [errors]="errors()" (gui-input-error)="onInputError($event)" (gui-input)="errors.set([])" ></gui-date> `,})export class DateBoundsComponent { errors = signal<string[]>([]);
// The element reports what it rejects; show it with errors. onInputError(event: Event) { this.errors.set([(event as DateEvent<'gui-input-error'>).detail.message]); }}<script setup lang="ts">import { ref } from 'vue';import '@golemui/gui-components/date-input';import '@golemui/gui-components/index.css';
const errors = ref<string[]>([]);</script>
<template> <!-- The element reports what it rejects; show it with errors. --> <gui-date id="date-start" label="Start date" hint="Any day in 2027." min-date="2027-01-01" max-date="2027-12-31" :errors.prop="errors" @gui-input-error="errors = [$event.detail.message]" @gui-input="errors = []" ></gui-date></template>import { LitElement, html } from 'lit';import { customElement, state } from 'lit/decorators.js';import type { GuiDateEvents, GuiEventMap } from '@golemui/gui-components';import '@golemui/gui-components/date-input';import '@golemui/gui-components/index.css';
type DateEvent<K extends keyof typeof GuiDateEvents> = GuiEventMap< typeof GuiDateEvents>[K];
@customElement('date-bounds')export class DateBounds extends LitElement { @state() private errors: string[] = [];
protected override createRenderRoot() { return this; }
// The element reports what it rejects; show it with errors. private onInputError(event: DateEvent<'gui-input-error'>) { this.errors = [event.detail.message]; }
private onInput() { this.errors = []; }
override render() { return html` <gui-date id="date-start" label="Start date" hint="Any day in 2027." min-date="2027-01-01" max-date="2027-12-31" .errors=${this.errors} @gui-input-error=${this.onInputError} @gui-input=${this.onInput} ></gui-date> `; }}<gui-date id="date-start" label="Start date" hint="Any day in 2027." min-date="2027-01-01" max-date="2027-12-31"></gui-date>
<script type="module"> const start = document.querySelector('#date-start');
// The element reports what it rejects; show it with errors. start.addEventListener('gui-input-error', (event) => { start.errors = [event.detail.message]; }); start.addEventListener('gui-input', () => { start.errors = []; });</script>gui-input-error also fires for an impossible date, such as February 31 (the value doesn’t
change, and the field stays invalid for its form until the date is fixed), and when focus leaves
a partly filled field (the value becomes empty). Each part keeps to
its own range as you type: a month of 13 becomes 12, and a year before 1900 becomes 1900.
Error messages
Section titled “Error messages”Each reason has an English default. Replace it on one element with min-date-message,
max-date-message, invalid-date-message and incomplete-message, or translate the defaults for
every element as in i18n. Type 2/30/2027 below, or leave the
field after typing only the month.
import { useState } from 'react';import { GuiDate } from '@golemui/gui-components/react';import '@golemui/gui-components/index.css';
export function DateMessages() { const [errors, setErrors] = useState<string[]>([]);
return ( <GuiDate id="date-trip" label="Return date" minDate="2027-01-01" minDateMessage="Pick a date in 2027 or later." invalidDateMessage="That month doesn't have that many days." incompleteMessage="Enter the day, the month and the year." errors={errors} onGuiInputError={(event) => setErrors([event.detail.message])} onGuiInput={() => setErrors([])} /> );}import { Component, CUSTOM_ELEMENTS_SCHEMA, signal } from '@angular/core';import type { GuiDateEvents, GuiEventMap } from '@golemui/gui-components';import '@golemui/gui-components/date-input';
type DateEvent<K extends keyof typeof GuiDateEvents> = GuiEventMap< typeof GuiDateEvents>[K];
@Component({ selector: 'app-date-messages', schemas: [CUSTOM_ELEMENTS_SCHEMA], template: ` <gui-date id="date-trip" label="Return date" min-date="2027-01-01" min-date-message="Pick a date in 2027 or later." invalid-date-message="That month doesn't have that many days." incomplete-message="Enter the day, the month and the year." [errors]="errors()" (gui-input-error)="onInputError($event)" (gui-input)="errors.set([])" ></gui-date> `,})export class DateMessagesComponent { errors = signal<string[]>([]);
onInputError(event: Event) { this.errors.set([(event as DateEvent<'gui-input-error'>).detail.message]); }}<script setup lang="ts">import { ref } from 'vue';import '@golemui/gui-components/date-input';import '@golemui/gui-components/index.css';
const errors = ref<string[]>([]);</script>
<template> <gui-date id="date-trip" label="Return date" min-date="2027-01-01" min-date-message="Pick a date in 2027 or later." invalid-date-message="That month doesn't have that many days." incomplete-message="Enter the day, the month and the year." :errors.prop="errors" @gui-input-error="errors = [$event.detail.message]" @gui-input="errors = []" ></gui-date></template>import { LitElement, html } from 'lit';import { customElement, state } from 'lit/decorators.js';import type { GuiDateEvents, GuiEventMap } from '@golemui/gui-components';import '@golemui/gui-components/date-input';import '@golemui/gui-components/index.css';
type DateEvent<K extends keyof typeof GuiDateEvents> = GuiEventMap< typeof GuiDateEvents>[K];
@customElement('date-messages')export class DateMessages extends LitElement { @state() private errors: string[] = [];
protected override createRenderRoot() { return this; }
private onInputError(event: DateEvent<'gui-input-error'>) { this.errors = [event.detail.message]; }
private onInput() { this.errors = []; }
override render() { return html` <gui-date id="date-trip" label="Return date" min-date="2027-01-01" min-date-message="Pick a date in 2027 or later." invalid-date-message="That month doesn't have that many days." incomplete-message="Enter the day, the month and the year." .errors=${this.errors} @gui-input-error=${this.onInputError} @gui-input=${this.onInput} ></gui-date> `; }}<gui-date id="date-trip" label="Return date" min-date="2027-01-01" min-date-message="Pick a date in 2027 or later." invalid-date-message="That month doesn't have that many days." incomplete-message="Enter the day, the month and the year."></gui-date>
<script type="module"> const trip = document.querySelector('#date-trip');
trip.addEventListener('gui-input-error', (event) => { trip.errors = [event.detail.message]; }); trip.addEventListener('gui-input', () => { trip.errors = []; });</script>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
can be focused and read, part by part, and a form submits its value.
import { GuiDate } from '@golemui/gui-components/react';import '@golemui/gui-components/index.css';
export function DateDisabledReadonly() { return ( <div className="gui-grid gui-grid--row"> <div className="gui-grid__cell"> <GuiDate label="Signed on" value="2026-03-02" disabled /> </div> <div className="gui-grid__cell"> <GuiDate label="Member since" value="2019-09-15" readOnly /> </div> </div> );}import { Component, CUSTOM_ELEMENTS_SCHEMA } from '@angular/core';import '@golemui/gui-components/date-input';
@Component({ selector: 'app-date-disabled-readonly', schemas: [CUSTOM_ELEMENTS_SCHEMA], template: ` <div class="gui-grid gui-grid--row"> <div class="gui-grid__cell"> <gui-date label="Signed on" value="2026-03-02" disabled></gui-date> </div> <div class="gui-grid__cell"> <gui-date label="Member since" value="2019-09-15" readonly></gui-date> </div> </div> `,})export class DateDisabledReadonlyComponent {}<script setup lang="ts">import '@golemui/gui-components/date-input';import '@golemui/gui-components/index.css';</script>
<template> <div class="gui-grid gui-grid--row"> <div class="gui-grid__cell"> <gui-date label="Signed on" value="2026-03-02" :disabled="true" ></gui-date> </div> <div class="gui-grid__cell"> <gui-date label="Member since" value="2019-09-15" :readOnly="true" ></gui-date> </div> </div></template>import { LitElement, html } from 'lit';import { customElement } from 'lit/decorators.js';import '@golemui/gui-components/date-input';import '@golemui/gui-components/index.css';
@customElement('date-disabled-readonly')export class DateDisabledReadonly extends LitElement { protected override createRenderRoot() { return this; }
override render() { return html` <div class="gui-grid gui-grid--row"> <div class="gui-grid__cell"> <gui-date label="Signed on" value="2026-03-02" disabled></gui-date> </div> <div class="gui-grid__cell"> <gui-date label="Member since" value="2019-09-15" readonly></gui-date> </div> </div> `; }}<div class="gui-grid gui-grid--row"> <div class="gui-grid__cell"> <gui-date label="Signed on" value="2026-03-02" disabled></gui-date> </div> <div class="gui-grid__cell"> <gui-date label="Member since" value="2019-09-15" readonly></gui-date> </div></div>icon takes the class names of an icon and shows it at the start of the field. The icon is
decorative and hidden from assistive technology. See Icons for
the ways to draw it.
import { GuiDate } from '@golemui/gui-components/react';import '@golemui/gui-components/index.css';
// Any icon font works: icon sets these classes on the icon element.// This one draws a Material Icons ligature.const css = ` .icon-event::before { content: 'event'; font-family: 'Material Icons'; }`;
export function DateIcon() { return ( <> <GuiDate label="Due date" icon="icon-event" /> <style>{css}</style> </> );}import { Component, CUSTOM_ELEMENTS_SCHEMA, ViewEncapsulation,} from '@angular/core';import '@golemui/gui-components/date-input';
@Component({ selector: 'app-date-icon', schemas: [CUSTOM_ELEMENTS_SCHEMA], // The element renders the icon itself: the style must not be scoped. encapsulation: ViewEncapsulation.None, template: `<gui-date label="Due date" icon="icon-event"></gui-date>`, styles: ` /* Any icon font works: icon sets these classes on the icon element. This one draws a Material Icons ligature. */ .icon-event::before { content: 'event'; font-family: 'Material Icons'; } `,})export class DateIconComponent {}<script setup lang="ts">import '@golemui/gui-components/date-input';import '@golemui/gui-components/index.css';</script>
<template> <gui-date label="Due date" icon="icon-event"></gui-date></template>
<style>/* Any icon font works: icon sets these classes on the icon element. This one draws a Material Icons ligature. */.icon-event::before { content: 'event'; font-family: 'Material Icons';}</style>import { LitElement, html } from 'lit';import { customElement } from 'lit/decorators.js';import '@golemui/gui-components/date-input';import '@golemui/gui-components/index.css';
@customElement('date-icon')export class DateIcon extends LitElement { protected override createRenderRoot() { return this; }
override render() { return html` <gui-date label="Due date" icon="icon-event"></gui-date>
<style> /* Any icon font works: icon sets these classes on the icon element. This one draws a Material Icons ligature. */ .icon-event::before { content: 'event'; font-family: 'Material Icons'; } </style> `; }}<gui-date label="Due date" icon="icon-event"></gui-date>
<style> /* Any icon font works: icon sets these classes on the icon element. This one draws a Material Icons ligature. */ .icon-event::before { content: 'event'; font-family: 'Material Icons'; }</style>In a form
Section titled “In a form”With a name, <gui-date> submits its ISO date under that name, such as
birthDate=1990-12-10, and nothing while it is empty. These block a submit:
required, while the field is empty;- a date before
min-dateor aftermax-date; - an impossible date, such as February 31.
The browser shows the element’s own message for the date, such as the max-date-message. Submit
the empty field, then a date after today, then a complete date of birth.
import { useState } from 'react';import { GuiButton, GuiDate } from '@golemui/gui-components/react';import '@golemui/gui-components/index.css';
// No date of birth after today.const now = new Date();const pad = (n: number) => String(n).padStart(2, '0');const today = `${now.getFullYear()}-${pad(now.getMonth() + 1)}-${pad(now.getDate())}`;
export function DateNativeForm() { const [result, setResult] = useState('');
return ( <form id="date-form" onSubmit={(event) => { event.preventDefault(); const data = Object.fromEntries(new FormData(event.currentTarget)); setResult(`Submitted ${JSON.stringify(data)}`); }} > <div className="gui-grid gui-grid--row"> <div className="gui-grid__cell gui-grid__cell--span-3"> <GuiDate id="date-form-birth" label="Date of birth" name="birthDate" localeId="en-GB" maxDate={today} maxDateMessage="Enter today or an earlier date." required /> </div> <div className="gui-grid__cell"> <GuiButton type="submit" label="Save" /> </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/date-input';
const pad = (n: number) => String(n).padStart(2, '0');const now = new Date();
@Component({ selector: 'app-date-native-form', schemas: [CUSTOM_ELEMENTS_SCHEMA], template: ` <form id="date-form" #form (submit)="onSubmit($event, form)"> <div class="gui-grid gui-grid--row"> <div class="gui-grid__cell gui-grid__cell--span-3"> <gui-date id="date-form-birth" label="Date of birth" name="birthDate" locale-id="en-GB" [maxDate]="today" max-date-message="Enter today or an earlier date." required ></gui-date> </div> <div class="gui-grid__cell"> <gui-button type="submit" label="Save"></gui-button> </div> </div> <p id="date-form-result" role="status">{{ result() }}</p> </form> `,})export class DateNativeFormComponent { // No date of birth after today. today = `${now.getFullYear()}-${pad(now.getMonth() + 1)}-${pad(now.getDate())}`; result = signal('');
onSubmit(event: Event, form: HTMLFormElement) { event.preventDefault(); const data = Object.fromEntries(new FormData(form)); this.result.set(`Submitted ${JSON.stringify(data)}`); }}<script setup lang="ts">import { ref, useTemplateRef } from 'vue';import '@golemui/gui-components/button';import '@golemui/gui-components/date-input';import '@golemui/gui-components/index.css';
const form = useTemplateRef('form');const result = ref('');
// No date of birth after today.const now = new Date();const pad = (n: number) => String(n).padStart(2, '0');const maxDate = `${now.getFullYear()}-${pad(now.getMonth() + 1)}-${pad(now.getDate())}`;
function onSubmit() { if (!form.value) return; const data = Object.fromEntries(new FormData(form.value)); result.value = `Submitted ${JSON.stringify(data)}`;}</script>
<template> <form id="date-form" ref="form" @submit.prevent="onSubmit"> <div class="gui-grid gui-grid--row"> <div class="gui-grid__cell gui-grid__cell--span-3"> <gui-date id="date-form-birth" label="Date of birth" name="birthDate" locale-id="en-GB" max-date-message="Enter today or an earlier date." :max-date="maxDate" :required="true" ></gui-date> </div> <div class="gui-grid__cell"> <gui-button type="submit" label="Save"></gui-button> </div> </div> <p id="date-form-result" role="status">{{ result }}</p> </form></template>import { LitElement, html } from 'lit';import { customElement, query, state } from 'lit/decorators.js';import '@golemui/gui-components/button';import '@golemui/gui-components/date-input';import '@golemui/gui-components/index.css';
// No date of birth after today.function today() { const now = new Date(); const pad = (n: number) => String(n).padStart(2, '0'); return `${now.getFullYear()}-${pad(now.getMonth() + 1)}-${pad(now.getDate())}`;}
@customElement('date-native-form')export class DateNativeForm extends LitElement { @query('form') private form!: HTMLFormElement; @state() private data?: Record<string, FormDataEntryValue>;
protected override createRenderRoot() { return this; }
private onSubmit(event: SubmitEvent) { event.preventDefault(); this.data = Object.fromEntries(new FormData(this.form)); }
override render() { return html` <form id="date-form" @submit=${this.onSubmit}> <div class="gui-grid gui-grid--row"> <div class="gui-grid__cell gui-grid__cell--span-3"> <gui-date id="date-form-birth" label="Date of birth" name="birthDate" locale-id="en-GB" max-date-message="Enter today or an earlier date." required .maxDate=${today()} ></gui-date> </div> <div class="gui-grid__cell"> <gui-button type="submit" label="Save"></gui-button> </div> </div> <p role="status"> ${this.data && `Submitted ${JSON.stringify(this.data)}`} </p> </form> `; }}<form id="date-form"> <div class="gui-grid gui-grid--row"> <div class="gui-grid__cell gui-grid__cell--span-3"> <gui-date id="date-form-birth" label="Date of birth" name="birthDate" locale-id="en-GB" max-date-message="Enter today or an earlier date." required ></gui-date> </div> <div class="gui-grid__cell"> <gui-button type="submit" label="Save"></gui-button> </div> </div> <p id="date-form-result" role="status"></p></form>
<script type="module"> const form = document.querySelector('#date-form');
// No date of birth after today. const now = new Date(); const pad = (n) => String(n).padStart(2, '0'); document.querySelector('#date-form-birth').maxDate = `${now.getFullYear()}-${pad(now.getMonth() + 1)}-${pad(now.getDate())}`;
form.addEventListener('submit', (event) => { event.preventDefault(); const data = Object.fromEntries(new FormData(form)); document.querySelector('#date-form-result').textContent = `Submitted ${JSON.stringify(data)}`; });</script>How the elements submit, validate and reset is in Native forms.
Styling
Section titled “Styling”Anatomy
Section titled “Anatomy”<gui-date> renders into the light DOM, so you can style every part with plain CSS. These are the
parts and their public classes, here with en-US parts, a hint, required and an error:
<gui-date class="gui-field"> <span class="gui-label" id="birth_label"> <span class="gui-label__text" >Date of birth<span aria-hidden="true"> *</span></span > <div class="gui-widget-hint" id="birth_hint" aria-hidden="true"> As on your passport. </div> </span> <div class="gui-widget"> <div role="group" id="birth" class="gui-widget-input gui-parts gui-parts-ring gui-date-input" aria-labelledby="birth_label" aria-describedby="birth_hint" aria-invalid="true" aria-errormessage="birth_errors" > <div class="gui-parts__touch-target gui-date-input__touch-target"> <input type="text" inputmode="numeric" role="spinbutton" class="gui-parts__part gui-date-input__part" data-type="month" maxlength="2" placeholder="mm" tabindex="0" aria-label="Month" aria-valuemin="1" aria-valuemax="12" aria-required="true" /> <div class="gui-parts__visual-underline gui-date-input__visual-underline" ></div> </div> <span class="gui-date-input__separator">/</span> <!-- The day part, like the month, with tabindex="-1" --> <span class="gui-date-input__separator">/</span> <div class="gui-parts__touch-target gui-date-input__touch-target"> <input type="text" inputmode="numeric" role="spinbutton" class="gui-parts__part gui-date-input__part gui-parts__year gui-date-input__year" data-type="year" maxlength="4" placeholder="yyyy" tabindex="-1" aria-label="Year" aria-valuemin="1900" aria-valuemax="9999" aria-required="true" /> <div class="gui-parts__visual-underline gui-date-input__visual-underline" ></div> </div> </div> </div> <div class="gui-validator" id="birth_errors" role="alert"> <div class="gui-validator__error">Enter a date in the past.</div> </div></gui-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 | The label, with the text and the hint. |
gui-label__text | The label text and the required marker. |
gui-widget-hint | The hint. |
gui-widget | The box around the control. |
gui-widget-input | The group of parts, the part that looks like a field. |
gui-parts, gui-date-input | The same group: the row of parts. |
gui-parts-ring | The same group: draws the focus ring and the error border around all the parts. |
gui-calendar--icon | On the group when there is an icon: it makes room for it. |
gui-parts__touch-target, gui-date-input__touch-target | The box around one part, its touch target. |
gui-parts__part, gui-date-input__part | One part: an <input> with its data-type, day, month or year. |
gui-parts__year, gui-date-input__year | The year part, which is wider. |
gui-parts__visual-underline | The line under the focused part. |
gui-date-input__separator | A separator between parts, from the locale. |
gui-widget-icon | The icon, with your icon classes. |
gui-validator | The errors container, there while there are errors. The alert role announces them. |
gui-validator__error | One error message. |
The ids derive from uid, or from a generated id such as gui-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. |
--gui-bg-surface | The field’s background. |
--gui-border-default | The border. |
--gui-text-default | The text, the label, the parts and their underline. |
--gui-font-family | The font of the label and the field. |
--gui-font-sm | The text size of the label and the field. |
--gui-font-xs | The text size of the hint and the errors. |
--gui-text-secondary | The hint. |
--gui-border-focus | The border while a part is focused. |
--gui-shadow-focus | The focus ring. |
--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-space-1 | The space between the label, hint and errors. |
The parts use the browser’s monospace font, so the digits line up as you type.
/* Taller, rounder date fields in one form only. */.booking gui-date { --gui-widget-size: 48px; --gui-radius-md: 12px;}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 parts sit in a group that the
labelnames. Without a label the group has no name, and development builds log a warning. - Parts. Each part is a spin button named
Day,MonthorYear, with its range and current number. Rename them withday-aria-label,month-aria-labelandyear-aria-label, or translate them for every element under the keysday,monthandyear. - One tab stop. Only the first part is in the tab order. The arrow keys move between the parts.
- Description. The
hintis the group’s description, througharia-describedby. - Errors. While errors show, the group has
aria-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.
| Key | Action |
|---|---|
| Tab | Moves focus to the first part, and out of the field. |
| 0 to 9 | Type the part. A full part moves focus to the next part that is still empty. |
| ↑ / ↓ | Add or subtract one, within the part’s range. The date updates at once. |
| ← / → | Move to the part on the left or right, in the order you see them. |
| Backspace / Delete | Delete digits. Leaving a part empty clears the value. |
Other keys don’t type into a part. What every element shares, such as translating the browser messages, is in Accessibility.