Skip to content
Components, switch product

Calendar

<gui-calendar> is a month calendar, always visible, to pick a date. Its value is an ISO date, such as 2026-10-05. For a field that opens a calendar in a popup, use the Date picker; to pick a time too, the Date-time calendar; for date ranges, the Range calendar.

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

The element’s entry point is @golemui/gui-components/calendar, which registers <gui-calendar> 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.

DeliveryDayField.tsx
import { useState } from 'react';
import { GuiCalendar } from '@golemui/gui-components/react';
import '@golemui/gui-components/index.css';
export function DeliveryDayField() {
const [deliveryDay, setDeliveryDay] = useState('');
return (
<GuiCalendar
label="Delivery day"
name="deliveryDay"
value={deliveryDay}
onGuiChange={(event) => setDeliveryDay(event.detail.value ?? '')}
/>
);
}

Properties

Property Type Default Description
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.
localeId attribute locale-id string – BCP 47 locale for formatting and parsing, such as en-US or es.
prevMonthIcon attribute prev-month-icon string '' Icon class name of the previous-month button.
nextMonthIcon attribute next-month-icon string '' Icon class name of the next-month button.
prevMonthAriaLabel attribute prev-month-aria-label string – Accessible name of the previous-month button. An empty value keeps the default.
nextMonthAriaLabel attribute next-month-aria-label string – Accessible name of the next-month button. An empty value keeps the default.
selectYearAriaLabel attribute select-year-aria-label string – Accessible name of the button that opens the year grid. An empty value keeps the default.
yearGridAriaLabel attribute year-grid-aria-label string – Accessible name of the year grid. An empty value keeps the default.
dayFormat attribute day-format 'numeric' | '2-digit' 'numeric' How day numbers are written.
weekdayFormat attribute weekday-format 'short' | 'long' | 'narrow' 'narrow' How weekday names are written in the header.
monthFormat attribute month-format 'numeric' | '2-digit' | 'long' | 'short' | 'narrow' 'long' How the month is written in the header.
minDate attribute min-date string – Earliest selectable date, as an ISO date (YYYY-MM-DD).
maxDate attribute max-date string – Latest selectable date, as an ISO date (YYYY-MM-DD).
disabledRanges attribute disabled-ranges DateRange[] – Dates that cannot be picked, as { start, end } ISO date ranges.
numberOfMonths attribute number-of-months number 1 Number of months shown side by side.
value string – The selected date, as an ISO date (YYYY-MM-DD).
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.

CSS custom properties

Property Default Description
--gui-calendar-width – Width of one month.
--gui-calendar-day-button-size – Size of each day.
--gui-calendar-change-month-button-width – Width of the previous- and next-month buttons.
--gui-calendar-change-month-button-height – Height of the previous- and next-month buttons.
--gui-calendar-year-button-width – Width of each year in the year grid.
--gui-calendar-year-button-height – Height of each year in the year grid.
--gui-calendar-year-grid-height – Height of the year grid.

value takes an ISO date (YYYY-MM-DD), and the calendar shows its month. Without a value it shows the current month. A picked day fires gui-input and then gui-change, with the ISO date in event.detail.value. The header’s year button opens a grid of years to jump to.

Value: 2026-11-18

Code of the example above
CalendarValue.tsx
import { useState } from 'react';
import { GuiCalendar } from '@golemui/gui-components/react';
import '@golemui/gui-components/index.css';
export function CalendarValue() {
const [value, setValue] = useState<string | undefined>('2026-11-18');
return (
<>
<GuiCalendar
id="calendar-workshop"
label="Workshop day"
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. disabled-ranges takes a JSON list of { "start", "end" } ISO dates, inclusive too; leave end out to disable one day. Those days can’t be picked, the keyboard skips them, the month buttons stop at the bounds, and the year grid only lists the years between them. Without bounds, the year grid goes from 1900 to 2099.

Code of the example above
CalendarBounds.tsx
import { GuiCalendar } from '@golemui/gui-components/react';
import type { DateRange } from '@golemui/gui-components';
import '@golemui/gui-components/index.css';
const closed: DateRange[] = [
{ start: '2026-12-24', end: '2026-12-26' },
{ start: '2026-12-31' },
];
export function CalendarBounds() {
return (
<GuiCalendar
label="Pickup day"
hint="From December to February. Closed over Christmas and on New Year's Eve."
value="2026-12-14"
minDate="2026-12-01"
maxDate="2027-02-28"
disabledRanges={closed}
/>
);
}

number-of-months shows that many months side by side. The month buttons move one month at a time.

Code of the example above
CalendarMonths.tsx
import { GuiCalendar } from '@golemui/gui-components/react';
import '@golemui/gui-components/index.css';
export function CalendarMonths() {
return (
<GuiCalendar label="Travel day" numberOfMonths={2} value="2026-12-14" />
);
}

locale-id takes a BCP 47 locale, such as fr-FR. It sets the names of the months and weekdays, their order in the header, the first day of the week and the names screen readers read for the days. Without it, the calendar uses English (en), with the week starting on Sunday. Set locale-id for another language.

Three attributes change how the calendar writes its parts, with the formats of Intl.DateTimeFormat:

AttributeValuesDefault
weekday-formatnarrow, short, longnarrow
month-formatlong, short, narrow, numeric, 2-digitlong
day-formatnumeric, 2-digitnumeric
Code of the example above
CalendarFormats.tsx
import { GuiCalendar } from '@golemui/gui-components/react';
import '@golemui/gui-components/index.css';
export function CalendarFormats() {
return (
<GuiCalendar
label="Date de livraison"
localeId="fr-FR"
weekdayFormat="short"
monthFormat="short"
dayFormat="2-digit"
value="2026-12-14"
/>
);
}

The month buttons take prev-month-icon and next-month-icon to show icons instead of their carets.

With a name, <gui-calendar> submits the picked ISO date under that name, such as tastingDay=2026-11-18, and nothing while no day is picked. required blocks the submit until the user picks a day. A disabled calendar can’t be used and isn’t submitted; a readonly one shows its value and is submitted, but its days can’t be picked.

Code of the example above
CalendarNativeForm.tsx
import { useState } from 'react';
import { GuiButton, GuiCalendar } from '@golemui/gui-components/react';
import '@golemui/gui-components/index.css';
export function CalendarNativeForm() {
const [result, setResult] = useState('');
return (
<form
id="calendar-form"
onSubmit={(event) => {
event.preventDefault();
const data = Object.fromEntries(new FormData(event.currentTarget));
setResult(`Submitted ${JSON.stringify(data)}`);
}}
>
<GuiCalendar label="Tasting day" name="tastingDay" required />
<p>
<GuiButton type="submit" label="Book a table" />
</p>
<p role="status">{result}</p>
</form>
);
}

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

<gui-calendar> renders into the light DOM, so you can style every part with plain CSS. These are the parts and their public classes, here in en-US with a hint, required and min-date:

gui-calendar
<gui-calendar class="gui-field">
<label class="gui-label" for="delivery" id="delivery_calendar_label">
<span class="gui-label__text"
>Delivery day<span aria-hidden="true"> *</span></span
>
<div class="gui-widget-hint" id="delivery_hint" aria-hidden="true">
We deliver on weekdays.
</div>
</label>
<div class="gui-widget">
<div
class="gui-calendar-input"
role="group"
id="delivery_calendar"
aria-labelledby="delivery_calendar_label"
aria-describedby="delivery_hint"
>
<div class="gui-visually-hidden" aria-live="polite">October 2026</div>
<div class="gui-calendar__container">
<button
type="button"
class="gui-button gui-calendar__month-button gui-calendar__month-button--prev"
disabled
aria-label="Previous month"
>
<span class="gui-caret gui-caret--left" aria-hidden="true"></span>
</button>
<div class="gui-calendar__months-grid">
<div class="gui-calendar__panel">
<header class="gui-calendar__header">
<h2>
<span class="gui-calendar__month-name">October</span>
<button
type="button"
class="gui-calendar__year-selector"
aria-expanded="false"
aria-label="Select year, 2026"
>
<span class="gui-calendar__year-value">2026</span>
<span class="gui-calendar__year-arrow" aria-hidden="true">
<span class="gui-caret" aria-hidden="true"></span>
</span>
</button>
</h2>
</header>
<div
class="gui-calendar__days-grid"
role="grid"
aria-label="October 2026"
>
<div role="row" class="gui-calendar__rows">
<span class="gui-calendar__weekday" role="columnheader">S</span>
<!-- The other six weekdays -->
</div>
<div role="row" class="gui-calendar__rows">
<button
type="button"
role="gridcell"
class="gui-calendar__day-button disabled other-month"
tabindex="-1"
disabled
aria-label="Sunday, September 27, 2026"
data-date="2026-09-27"
aria-selected="false"
>
27
</button>
<button
type="button"
role="gridcell"
class="gui-calendar__day-button disabled"
tabindex="-1"
aria-disabled="true"
aria-label="Thursday, October 1, 2026"
data-date="2026-10-01"
aria-selected="false"
>
1
</button>
<!-- The other days of the week -->
</div>
<!-- The other weeks, with the classes today and selected on those days -->
</div>
</div>
</div>
<button
type="button"
class="gui-button gui-calendar__month-button gui-calendar__month-button--next"
aria-label="Next month"
>
<span class="gui-caret gui-caret--right" aria-hidden="true"></span>
</button>
</div>
</div>
</div>
</gui-calendar>

While the year grid is open, it takes the place of the first month’s days grid:

The year grid
<div class="gui-calendar__year-grid" role="grid" aria-label="Year selection">
<div role="row" class="gui-calendar__year-row">
<button
type="button"
role="gridcell"
class="gui-calendar__year-button current"
tabindex="0"
data-year="2026"
aria-selected="true"
>
2026
</button>
<!-- Four years a row -->
</div>
</div>
ClassPart
gui-fieldThe element itself.
gui-label, gui-label__text, gui-widget-hintThe label, its text with the required marker, and the hint.
gui-widgetThe box around the calendar.
gui-calendar-inputThe calendar’s card, with its border.
gui-calendar__containerThe row of the month buttons and the months.
gui-calendar__month-buttonA month button, with --prev or --next.
gui-calendar__months-gridThe months, side by side.
gui-calendar__panelOne month.
gui-calendar__headerA month’s header, with its name and year.
gui-calendar__month-nameThe month’s name.
gui-calendar__year-selectorThe year button that opens the year grid.
gui-calendar__year-value, gui-calendar__year-arrowThe year and its caret, which turns while the grid is open.
gui-calendar__days-gridA month’s days, seven columns.
gui-calendar__rowsA week, or the weekday names.
gui-calendar__weekdayA weekday name.
gui-calendar__day-buttonA day.
today, selected, disabled, other-monthOn a day: today, the value, a day that can’t be picked, a day of another month.
gui-calendar__year-grid, gui-calendar__year-rowThe year grid and a row of four years.
gui-calendar__year-buttonA year, with current on the year shown.
gui-visually-hiddenThe live region that announces the month shown.
gui-validator, gui-validator__errorThe errors container, there while there are errors, and one message.

The ids derive from uid, or from a generated id when you don’t set one. Select by class or by tag, not by id.

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

TokenWhat it styles
--gui-calendar-day-button-sizeThe width and height of a day. Defaults to --gui-widget-size.
--gui-calendar-change-month-button-widthThe width of the month buttons.
--gui-calendar-change-month-button-heightThe height of the month buttons.
--gui-calendar-year-button-widthThe width of a year in the year grid.
--gui-calendar-year-button-heightThe height of a year in the year grid.
--gui-calendar-year-grid-heightThe height of the year grid, which scrolls.
--gui-bg-defaultThe card’s background.
--gui-border-defaultThe card’s and the year grid’s border, and a day under the mouse.
--gui-radius-mdThe corner radius of the card, the days and the years.
--gui-space-3The card’s padding.
--gui-space-1The gaps of the year grid.
--gui-text-defaultThe days, the years and the weekday names.
--gui-text-secondaryThe background of a day or a year under the mouse.
--gui-color-whiteThe text of the selected day and the month buttons’ carets.
--gui-intent-primaryThe selected day and the month buttons.
--gui-intent-primary-activeThe border of today and of the current year.
--gui-bg-disabledThe days that can’t be picked, and a disabled card.
--gui-text-disabledThe text of a disabled card.
--gui-border-focusThe focus ring of the buttons, the days and the years.
--gui-shadow-focusThe focus ring of the month and year buttons.
--gui-intent-errorThe card’s border and the messages when there are errors.
styles.css
/* Roomier days, and a taller year grid. */
:root {
--gui-calendar-day-button-size: 44px;
--gui-calendar-year-grid-height: 280px;
}

To restyle beyond the tokens, see Overriding styles; for dark mode and the Clay theme, see Themes and dark mode.

  • Name. The label names the calendar’s group. Each month’s days grid is named by its month and year, and each day by its full date, such as Thursday, October 1, 2026, in the locale.
  • States. The picked day has aria-selected="true" and today aria-current="date". A day out of bounds or in disabled-ranges stays in the grid with aria-disabled="true", so screen readers still read it.
  • Month changes. A live region announces the months shown each time they change.
  • Buttons. The month buttons are named Previous month and Next month, the year button Select year followed by the year, and the year grid Year selection. Rename them with prev-month-aria-label, next-month-aria-label, select-year-aria-label and year-grid-aria-label, or translate them for every element under the keys previousMonth, nextMonth, selectYear and yearSelection.
  • Description. The hint is the group’s description, through aria-describedby.

In the days grid:

KeyAction
TabMoves between the month buttons, the year button and the days grid, which takes focus on today or the picked day.
← / →Move to the previous or next day, mirrored right to left. Past the first or last day, show the next month.
↑ / ↓Move to the same weekday a week before or after.
Page Up / Page DownMove to the same day in the previous or next month. A day the month lacks becomes its last day, so January 31 moves to February 28.
Shift + Page Up / Page DownMove to the same day in the previous or next year.
Home / EndMove to the first or last day of the week, within the month. The week starts on the locale’s first day.
Enter / SpacePicks the focused day.

The arrows, Page Up, Page Down, Home and End move focus only; they don’t pick a day. The calendar changes month when the day isn’t shown. They skip the days that can’t be picked, and past min-date or max-date they stop on the nearest day that can be.

In the year grid:

KeyAction
← / →Move to the previous or next year.
↑ / ↓Move four years back or forward, a row.
Enter / SpaceShows that year, on the same month, and returns to the days.
EscapeCloses the year grid and returns focus to the year button.

What every element shares, such as translating the browser messages, is in Accessibility.