Skip to content
Components, switch product

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.

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

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.

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

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.

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.

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

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

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

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.

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

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.

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.

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

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.

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

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.

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

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-date or after max-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.

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

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

<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
<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>
ClassPart
gui-fieldThe element itself. Lines its parts up with the fields next to it in a gui-grid row.
gui-labelThe label, with the text and the hint.
gui-label__textThe label text and the required marker.
gui-widget-hintThe hint.
gui-widgetThe box around the control.
gui-widget-inputThe group of parts, the part that looks like a field.
gui-parts, gui-date-inputThe same group: the row of parts.
gui-parts-ringThe same group: draws the focus ring and the error border around all the parts.
gui-calendar--iconOn the group when there is an icon: it makes room for it.
gui-parts__touch-target, gui-date-input__touch-targetThe box around one part, its touch target.
gui-parts__part, gui-date-input__partOne part: an <input> with its data-type, day, month or year.
gui-parts__year, gui-date-input__yearThe year part, which is wider.
gui-parts__visual-underlineThe line under the focused part.
gui-date-input__separatorA separator between parts, from the locale.
gui-widget-iconThe icon, with your icon classes.
gui-validatorThe errors container, there while there are errors. The alert role announces them.
gui-validator__errorOne 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.

The field reads these design tokens. Set them on :root to change every field, or on any element to change the fields inside it:

TokenWhat it styles
--gui-widget-sizeThe height of the field.
--gui-radius-mdThe corner radius.
--gui-bg-surfaceThe field’s background.
--gui-border-defaultThe border.
--gui-text-defaultThe text, the label, the parts and their underline.
--gui-font-familyThe font of the label and the field.
--gui-font-smThe text size of the label and the field.
--gui-font-xsThe text size of the hint and the errors.
--gui-text-secondaryThe hint.
--gui-border-focusThe border while a part is focused.
--gui-shadow-focusThe focus ring.
--gui-intent-errorThe border and the messages when there are errors.
--gui-shadow-focus-errorThe focus ring when there are errors.
--gui-bg-disabledThe background when disabled.
--gui-text-disabledThe text when disabled.
--gui-space-1The space between the label, hint and errors.

The parts use the browser’s monospace font, so the digits line up as you type.

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

  • Name. The parts sit in a group that the label names. Without a label the group has no name, and development builds log a warning.
  • Parts. Each part is a spin button named Day, Month or Year, with its range and current number. Rename them with day-aria-label, month-aria-label and year-aria-label, or translate them for every element under the keys day, month and year.
  • One tab stop. Only the first part is in the tab order. The arrow keys move between the parts.
  • Description. The hint is the group’s description, through aria-describedby.
  • Errors. While errors show, the group has aria-invalid="true" and points at them with aria-errormessage, and screen readers announce new errors as they appear.
  • Required. required sets aria-required on every part; the visual marker is hidden from screen readers.
KeyAction
TabMoves focus to the first part, and out of the field.
0 to 9Type 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 / DeleteDelete 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.