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.
import { GuiCalendar } from '@golemui/gui-components/react';import '@golemui/gui-components/index.css';
export function CalendarBasic() { return <GuiCalendar label="Delivery day" />;}import { Component, CUSTOM_ELEMENTS_SCHEMA } from '@angular/core';import '@golemui/gui-components/calendar';
@Component({ selector: 'app-calendar-basic', schemas: [CUSTOM_ELEMENTS_SCHEMA], template: `<gui-calendar label="Delivery day"></gui-calendar>`,})export class CalendarBasicComponent {}<script setup lang="ts">import '@golemui/gui-components/calendar';import '@golemui/gui-components/index.css';</script>
<template> <gui-calendar label="Delivery day"></gui-calendar></template>import { LitElement, html } from 'lit';import { customElement } from 'lit/decorators.js';import '@golemui/gui-components/calendar';import '@golemui/gui-components/index.css';
@customElement('calendar-basic')export class CalendarBasic extends LitElement { protected override createRenderRoot() { return this; }
override render() { return html`<gui-calendar label="Delivery day"></gui-calendar>`; }}<gui-calendar label="Delivery day"></gui-calendar>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.
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 ?? '')} /> );}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, GuiCalendarEvents } from '@golemui/gui-components';import '@golemui/gui-components/calendar';
type ChangeEvent = GuiEventMap<typeof GuiCalendarEvents>['gui-change'];
@Component({ selector: 'app-delivery-day-field', schemas: [CUSTOM_ELEMENTS_SCHEMA], template: ` <gui-calendar label="Delivery day" name="deliveryDay" [value]="deliveryDay" (gui-change)="onChange($event)" ></gui-calendar> `,})export class DeliveryDayFieldComponent { deliveryDay = '';
onChange(event: Event) { this.deliveryDay = (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/calendar';import '@golemui/gui-components/index.css';
const deliveryDay = ref('');</script>
<template> <gui-calendar label="Delivery day" name="deliveryDay" :value="deliveryDay" @gui-change="deliveryDay = $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/calendar';import '@golemui/gui-components/index.css';
@customElement('delivery-day-field')export class DeliveryDayField extends LitElement { @state() private deliveryDay = '';
protected override createRenderRoot() { return this; }
override render() { return html` <gui-calendar label="Delivery day" name="deliveryDay" .value=${this.deliveryDay} @gui-change=${(event: CustomEvent) => (this.deliveryDay = event.detail.value)} ></gui-calendar> `; }}With any bundler, such as Vite. Import the element once, anywhere, and use the tag in your markup.
<gui-calendar label="Delivery day" name="deliveryDay"></gui-calendar>
<script type="module" src="./main.js"></script>import '@golemui/gui-components/calendar';import '@golemui/gui-components/index.css';
const element = document.querySelector('gui-calendar');
element.addEventListener('gui-change', (event) => { console.log(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. |
Examples
Section titled “Examples”Value and events
Section titled “Value and events”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
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> </> );}import { Component, CUSTOM_ELEMENTS_SCHEMA, signal } from '@angular/core';import type { GuiCalendarEvents, GuiEventMap } from '@golemui/gui-components';import '@golemui/gui-components/calendar';
type CalendarEvent<K extends keyof typeof GuiCalendarEvents> = GuiEventMap< typeof GuiCalendarEvents>[K];
@Component({ selector: 'app-calendar-value', schemas: [CUSTOM_ELEMENTS_SCHEMA], template: ` <gui-calendar id="calendar-workshop" label="Workshop day" [value]="value()" (gui-change)="onChange($event)" ></gui-calendar> <p id="calendar-workshop-value" role="status">Value: {{ value() }}</p> `,})export class CalendarValueComponent { value = signal<string | undefined>('2026-11-18');
onChange(event: Event) { this.value.set((event as CalendarEvent<'gui-change'>).detail.value); }}<script setup lang="ts">import { ref } from 'vue';import '@golemui/gui-components/calendar';import '@golemui/gui-components/index.css';
const value = ref<string | undefined>('2026-11-18');</script>
<template> <gui-calendar id="calendar-workshop" label="Workshop day" :value="value" @gui-change="value = $event.detail.value" ></gui-calendar> <p id="calendar-workshop-value" role="status">Value: {{ value }}</p></template>import { LitElement, html } from 'lit';import { customElement, state } from 'lit/decorators.js';import type { GuiCalendarEvents, GuiEventMap } from '@golemui/gui-components';import '@golemui/gui-components/calendar';import '@golemui/gui-components/index.css';
type CalendarEvent<K extends keyof typeof GuiCalendarEvents> = GuiEventMap< typeof GuiCalendarEvents>[K];
@customElement('calendar-value')export class CalendarValue extends LitElement { @state() private value: string | undefined = '2026-11-18';
protected override createRenderRoot() { return this; }
private onChange(event: CalendarEvent<'gui-change'>) { this.value = event.detail.value; }
override render() { return html` <gui-calendar id="calendar-workshop" label="Workshop day" .value=${this.value} @gui-change=${this.onChange} ></gui-calendar> <p role="status">Value: ${this.value}</p> `; }}<gui-calendar id="calendar-workshop" label="Workshop day" value="2026-11-18"></gui-calendar><p id="calendar-workshop-value" role="status">Value: 2026-11-18</p>
<script type="module"> const workshop = document.querySelector('#calendar-workshop');
workshop.addEventListener('gui-change', (event) => { document.querySelector('#calendar-workshop-value').textContent = `Value: ${event.detail.value}`; });</script>Bounds and disabled dates
Section titled “Bounds and disabled dates”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.
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} /> );}import { Component, CUSTOM_ELEMENTS_SCHEMA } from '@angular/core';import type { DateRange } from '@golemui/gui-components';import '@golemui/gui-components/calendar';
@Component({ selector: 'app-calendar-bounds', schemas: [CUSTOM_ELEMENTS_SCHEMA], template: ` <gui-calendar label="Pickup day" hint="From December to February. Closed over Christmas and on New Year's Eve." value="2026-12-14" min-date="2026-12-01" max-date="2027-02-28" [disabledRanges]="closed" ></gui-calendar> `,})export class CalendarBoundsComponent { closed: 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/calendar';import '@golemui/gui-components/index.css';
const disabledRanges: DateRange[] = [ { start: '2026-12-24', end: '2026-12-26' }, { start: '2026-12-31' },];</script>
<template> <gui-calendar label="Pickup day" hint="From December to February. Closed over Christmas and on New Year's Eve." value="2026-12-14" min-date="2026-12-01" max-date="2027-02-28" :disabledRanges.prop="disabledRanges" ></gui-calendar></template>import { LitElement, html } from 'lit';import { customElement } from 'lit/decorators.js';import type { DateRange } from '@golemui/gui-components';import '@golemui/gui-components/calendar';import '@golemui/gui-components/index.css';
const closedDays: DateRange[] = [ { start: '2026-12-24', end: '2026-12-26' }, { start: '2026-12-31' },];
@customElement('calendar-bounds')export class CalendarBounds extends LitElement { protected override createRenderRoot() { return this; }
override render() { return html` <gui-calendar label="Pickup day" hint="From December to February. Closed over Christmas and on New Year's Eve." value="2026-12-14" min-date="2026-12-01" max-date="2027-02-28" .disabledRanges=${closedDays} ></gui-calendar> `; }}<gui-calendar label="Pickup day" hint="From December to February. Closed over Christmas and on New Year's Eve." value="2026-12-14" min-date="2026-12-01" max-date="2027-02-28" disabled-ranges='[{ "start": "2026-12-24", "end": "2026-12-26" }, { "start": "2026-12-31" }]'></gui-calendar>Several months
Section titled “Several months”number-of-months shows that many months side by side. The month buttons move one month at a
time.
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" /> );}import { Component, CUSTOM_ELEMENTS_SCHEMA } from '@angular/core';import '@golemui/gui-components/calendar';
@Component({ selector: 'app-calendar-months', schemas: [CUSTOM_ELEMENTS_SCHEMA], template: ` <gui-calendar label="Travel day" [numberOfMonths]="2" value="2026-12-14" ></gui-calendar> `,})export class CalendarMonthsComponent {}<script setup lang="ts">import '@golemui/gui-components/calendar';import '@golemui/gui-components/index.css';</script>
<template> <gui-calendar label="Travel day" :number-of-months="2" value="2026-12-14" ></gui-calendar></template>import { LitElement, html } from 'lit';import { customElement } from 'lit/decorators.js';import '@golemui/gui-components/calendar';import '@golemui/gui-components/index.css';
@customElement('calendar-months')export class CalendarMonths extends LitElement { protected override createRenderRoot() { return this; }
override render() { return html` <gui-calendar label="Travel day" number-of-months="2" value="2026-12-14" ></gui-calendar> `; }}<gui-calendar label="Travel day" number-of-months="2" value="2026-12-14"></gui-calendar>Locale and formats
Section titled “Locale and formats”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:
| Attribute | Values | Default |
|---|---|---|
weekday-format | narrow, short, long | narrow |
month-format | long, short, narrow, numeric, 2-digit | long |
day-format | numeric, 2-digit | numeric |
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" /> );}import { Component, CUSTOM_ELEMENTS_SCHEMA } from '@angular/core';import '@golemui/gui-components/calendar';
@Component({ selector: 'app-calendar-formats', schemas: [CUSTOM_ELEMENTS_SCHEMA], template: ` <gui-calendar label="Date de livraison" locale-id="fr-FR" weekday-format="short" month-format="short" day-format="2-digit" value="2026-12-14" ></gui-calendar> `,})export class CalendarFormatsComponent {}<script setup lang="ts">import '@golemui/gui-components/calendar';import '@golemui/gui-components/index.css';</script>
<template> <gui-calendar label="Date de livraison" locale-id="fr-FR" weekday-format="short" month-format="short" day-format="2-digit" value="2026-12-14" ></gui-calendar></template>import { LitElement, html } from 'lit';import { customElement } from 'lit/decorators.js';import '@golemui/gui-components/calendar';import '@golemui/gui-components/index.css';
@customElement('calendar-formats')export class CalendarFormats extends LitElement { protected override createRenderRoot() { return this; }
override render() { return html` <gui-calendar label="Date de livraison" locale-id="fr-FR" weekday-format="short" month-format="short" day-format="2-digit" value="2026-12-14" ></gui-calendar> `; }}<gui-calendar label="Date de livraison" locale-id="fr-FR" weekday-format="short" month-format="short" day-format="2-digit" value="2026-12-14"></gui-calendar>The month buttons take prev-month-icon and next-month-icon to show
icons instead of their carets.
In a form
Section titled “In a form”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.
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> );}import { Component, CUSTOM_ELEMENTS_SCHEMA, signal } from '@angular/core';import '@golemui/gui-components/button';import '@golemui/gui-components/calendar';
@Component({ selector: 'app-calendar-native-form', schemas: [CUSTOM_ELEMENTS_SCHEMA], template: ` <form id="calendar-form" #form (submit)="onSubmit($event, form)"> <gui-calendar label="Tasting day" name="tastingDay" required ></gui-calendar> <p> <gui-button type="submit" label="Book a table"></gui-button> </p> <p id="calendar-form-result" role="status">{{ result() }}</p> </form> `,})export class CalendarNativeFormComponent { 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/calendar';import '@golemui/gui-components/index.css';
const form = useTemplateRef('form');const result = ref('');
function onSubmit() { if (!form.value) return; const data = Object.fromEntries(new FormData(form.value)); result.value = `Submitted ${JSON.stringify(data)}`;}</script>
<template> <form id="calendar-form" ref="form" @submit.prevent="onSubmit"> <gui-calendar label="Tasting day" name="tastingDay" :required="true" ></gui-calendar> <p> <gui-button type="submit" label="Book a table"></gui-button> </p> <p id="calendar-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/calendar';import '@golemui/gui-components/index.css';
@customElement('calendar-native-form')export class CalendarNativeForm 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="calendar-form" @submit=${this.onSubmit}> <gui-calendar label="Tasting day" name="tastingDay" required ></gui-calendar> <p> <gui-button type="submit" label="Book a table"></gui-button> </p> <p role="status"> ${this.data && `Submitted ${JSON.stringify(this.data)}`} </p> </form> `; }}<form id="calendar-form"> <gui-calendar label="Tasting day" name="tastingDay" required></gui-calendar> <p> <gui-button type="submit" label="Book a table"></gui-button> </p> <p id="calendar-form-result" role="status"></p></form>
<script type="module"> const form = document.querySelector('#calendar-form');
form.addEventListener('submit', (event) => { event.preventDefault(); const data = Object.fromEntries(new FormData(form)); document.querySelector('#calendar-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-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 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:
<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>| Class | Part |
|---|---|
gui-field | The element itself. |
gui-label, gui-label__text, gui-widget-hint | The label, its text with the required marker, and the hint. |
gui-widget | The box around the calendar. |
gui-calendar-input | The calendar’s card, with its border. |
gui-calendar__container | The row of the month buttons and the months. |
gui-calendar__month-button | A month button, with --prev or --next. |
gui-calendar__months-grid | The months, side by side. |
gui-calendar__panel | One month. |
gui-calendar__header | A month’s header, with its name and year. |
gui-calendar__month-name | The month’s name. |
gui-calendar__year-selector | The year button that opens the year grid. |
gui-calendar__year-value, gui-calendar__year-arrow | The year and its caret, which turns while the grid is open. |
gui-calendar__days-grid | A month’s days, seven columns. |
gui-calendar__rows | A week, or the weekday names. |
gui-calendar__weekday | A weekday name. |
gui-calendar__day-button | A day. |
today, selected, disabled, other-month | On a day: today, the value, a day that can’t be picked, a day of another month. |
gui-calendar__year-grid, gui-calendar__year-row | The year grid and a row of four years. |
gui-calendar__year-button | A year, with current on the year shown. |
gui-visually-hidden | The live region that announces the month shown. |
gui-validator, gui-validator__error | The 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.
Tokens
Section titled “Tokens”The calendar reads these design tokens. Set them on :root to change every calendar, or on any
element to change the calendars inside it:
| Token | What it styles |
|---|---|
--gui-calendar-day-button-size | The width and height of a day. Defaults to --gui-widget-size. |
--gui-calendar-change-month-button-width | The width of the month buttons. |
--gui-calendar-change-month-button-height | The height of the month buttons. |
--gui-calendar-year-button-width | The width of a year in the year grid. |
--gui-calendar-year-button-height | The height of a year in the year grid. |
--gui-calendar-year-grid-height | The height of the year grid, which scrolls. |
--gui-bg-default | The card’s background. |
--gui-border-default | The card’s and the year grid’s border, and a day under the mouse. |
--gui-radius-md | The corner radius of the card, the days and the years. |
--gui-space-3 | The card’s padding. |
--gui-space-1 | The gaps of the year grid. |
--gui-text-default | The days, the years and the weekday names. |
--gui-text-secondary | The background of a day or a year under the mouse. |
--gui-color-white | The text of the selected day and the month buttons’ carets. |
--gui-intent-primary | The selected day and the month buttons. |
--gui-intent-primary-active | The border of today and of the current year. |
--gui-bg-disabled | The days that can’t be picked, and a disabled card. |
--gui-text-disabled | The text of a disabled card. |
--gui-border-focus | The focus ring of the buttons, the days and the years. |
--gui-shadow-focus | The focus ring of the month and year buttons. |
--gui-intent-error | The card’s border and the messages when there are errors. |
/* 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.
Accessibility
Section titled “Accessibility”- Name. The
labelnames the calendar’s group. Each month’s days grid is named by its month and year, and each day by its full date, such asThursday, October 1, 2026, in the locale. - States. The picked day has
aria-selected="true"and todayaria-current="date". A day out of bounds or indisabled-rangesstays in the grid witharia-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 monthandNext month, the year buttonSelect yearfollowed by the year, and the year gridYear selection. Rename them withprev-month-aria-label,next-month-aria-label,select-year-aria-labelandyear-grid-aria-label, or translate them for every element under the keyspreviousMonth,nextMonth,selectYearandyearSelection. - Description. The
hintis the group’s description, througharia-describedby.
In the days grid:
| Key | Action |
|---|---|
| Tab | Moves 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 Down | Move 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 Down | Move to the same day in the previous or next year. |
| Home / End | Move to the first or last day of the week, within the month. The week starts on the locale’s first day. |
| Enter / Space | Picks 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:
| Key | Action |
|---|---|
| ← / → | Move to the previous or next year. |
| ↑ / ↓ | Move four years back or forward, a row. |
| Enter / Space | Shows that year, on the same month, and returns to the days. |
| Escape | Closes the year grid and returns focus to the year button. |
What every element shares, such as translating the browser messages, is in Accessibility.