Calendar
ui-calendar shows one month as a grid of day buttons. Clicking a day selects it, and the selected date is the
element's value in YYYY-MM-DD form. The calendar is form-associated. Give it a name and the form submits the selected
date in the same format a native <input type="date"> uses.
Dates are handled as plain calendar days in UTC, so a value never shifts across time zones. Today is the user's
local date. Month and weekday names come from Intl.DateTimeFormat, and weeks always start on Sunday.
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>Calendar demo</title>
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/@realmorg/realm@alpha/dist/realm.css">
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/@realmorg/ui@alpha/dist/theme.css">
<script type="module" src="https://cdn.jsdelivr.net/npm/@realmorg/realm@alpha/dist/realm.js"></script>
</head>
<body>
<import-element from="https://cdn.jsdelivr.net/npm/@realmorg/ui@alpha/components/calendar.html"></import-element>
<ui-calendar></ui-calendar>
</body>
</html>Examples
Selected date
Set value to preselect a date. The calendar opens on that date's month.
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>Calendar demo</title>
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/@realmorg/realm@alpha/dist/realm.css">
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/@realmorg/ui@alpha/dist/theme.css">
<script type="module" src="https://cdn.jsdelivr.net/npm/@realmorg/realm@alpha/dist/realm.js"></script>
</head>
<body>
<import-element from="https://cdn.jsdelivr.net/npm/@realmorg/ui@alpha/components/calendar.html"></import-element>
<ui-calendar value="2026-03-14"></ui-calendar>
</body>
</html>Minimum and maximum
Days before min or after max are disabled and cannot be selected.
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>Calendar demo</title>
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/@realmorg/realm@alpha/dist/realm.css">
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/@realmorg/ui@alpha/dist/theme.css">
<script type="module" src="https://cdn.jsdelivr.net/npm/@realmorg/realm@alpha/dist/realm.js"></script>
</head>
<body>
<import-element from="https://cdn.jsdelivr.net/npm/@realmorg/ui@alpha/components/calendar.html"></import-element>
<ui-calendar value="2026-06-10" min="2026-06-05" max="2026-06-24"></ui-calendar>
</body>
</html>Starting month
month (YYYY-MM) sets the month shown first when there is no value to show.
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>Calendar demo</title>
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/@realmorg/realm@alpha/dist/realm.css">
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/@realmorg/ui@alpha/dist/theme.css">
<script type="module" src="https://cdn.jsdelivr.net/npm/@realmorg/realm@alpha/dist/realm.js"></script>
</head>
<body>
<import-element from="https://cdn.jsdelivr.net/npm/@realmorg/ui@alpha/components/calendar.html"></import-element>
<ui-calendar month="2026-12"></ui-calendar>
</body>
</html>Locale
locale is a BCP 47 language tag for the month title and weekday names. Without it the browser's default locale
is used.
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>Calendar demo</title>
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/@realmorg/realm@alpha/dist/realm.css">
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/@realmorg/ui@alpha/dist/theme.css">
<script type="module" src="https://cdn.jsdelivr.net/npm/@realmorg/realm@alpha/dist/realm.js"></script>
</head>
<body>
<import-element from="https://cdn.jsdelivr.net/npm/@realmorg/ui@alpha/components/calendar.html"></import-element>
<ui-calendar locale="fr-FR" value="2026-07-14"></ui-calendar>
<ui-calendar locale="ja-JP" value="2026-07-14"></ui-calendar>
</body>
</html>Listening for changes
The calendar fires change when a day is picked. Read the new date from value.
Selected:
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>Calendar demo</title>
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/@realmorg/realm@alpha/dist/realm.css">
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/@realmorg/ui@alpha/dist/theme.css">
<script type="module" src="https://cdn.jsdelivr.net/npm/@realmorg/realm@alpha/dist/realm.js"></script>
</head>
<body>
<import-element from="https://cdn.jsdelivr.net/npm/@realmorg/ui@alpha/components/calendar.html"></import-element>
<import-element from="./calendar-change-demo.html"></import-element>
<calendar-change-demo></calendar-change-demo>
</body>
</html><custom-element name="calendar-change-demo">
<script>
const picked = useState({ date: "nothing yet" });
function changed(calendar) {
picked.date = calendar.value;
}
</script>
<style>
:host {
display: grid;
gap: calc(var(--spacing) * 3);
justify-items: center;
}
p {
margin: 0;
font-size: var(--text-sm);
color: var(--muted-foreground);
}
</style>
<ui-calendar month="2026-10" onchange="changed(this)"></ui-calendar>
<p>Selected: <slot name="#picked.date"></slot></p>
</custom-element>In a form
The calendar submits its value under its name. Resetting the form returns it to the value attribute.
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>Calendar demo</title>
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/@realmorg/realm@alpha/dist/realm.css">
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/@realmorg/ui@alpha/dist/theme.css">
<script type="module" src="https://cdn.jsdelivr.net/npm/@realmorg/realm@alpha/dist/realm.js"></script>
</head>
<body>
<import-element from="https://cdn.jsdelivr.net/npm/@realmorg/ui@alpha/components/button.html"></import-element>
<import-element from="https://cdn.jsdelivr.net/npm/@realmorg/ui@alpha/components/calendar.html"></import-element>
<import-element from="./calendar-form-demo.html"></import-element>
<calendar-form-demo></calendar-form-demo>
</body>
</html><custom-element name="calendar-form-demo">
<script>
const result = useState({ text: "Submit the form to see its data." });
function submit(event) {
event.preventDefault();
result.text = [...new FormData(event.target)].map(([name, value]) => `${name}=${value}`).join("\n");
}
function reset() {
result.text = "The form was reset.";
}
</script>
<style>
:host {
display: grid;
gap: calc(var(--spacing) * 4);
justify-items: start;
}
form {
display: grid;
gap: calc(var(--spacing) * 3);
justify-items: start;
}
.actions {
display: flex;
gap: calc(var(--spacing) * 2);
}
output {
display: block;
box-sizing: border-box;
width: 100%;
padding: calc(var(--spacing) * 3);
border-radius: var(--radius-md);
background: var(--muted);
font-family: var(--font-mono);
font-size: var(--text-xs);
white-space: pre-wrap;
}
</style>
<form onsubmit="submit(event)" onreset="reset()">
<ui-calendar name="checkin" value="2026-08-03" min="2026-08-01"></ui-calendar>
<div class="actions">
<ui-button type="submit">Book</ui-button>
<ui-button type="reset" variant="outline">Reset</ui-button>
</div>
</form>
<output><slot name="#result.text"></slot></output>
</custom-element>API
Attributes
| Attribute | Type | Default | Description |
|---|---|---|---|
value |
string | "" |
Selected date as YYYY-MM-DD. Empty means no selection. |
month |
string | Month shown first, as YYYY-MM. Used when the calendar is created. Defaults to the month of value, or the current month. |
|
min |
string | Earliest selectable date, YYYY-MM-DD. |
|
max |
string | Latest selectable date, YYYY-MM-DD. |
|
locale |
string | Locale for the month title and weekday names. Defaults to the browser locale. |
Properties
| Property | Type | Description |
|---|---|---|
value |
string | The selected date. Setting it updates the selection and the submitted value. |
Events
| Event | Detail | Description |
|---|---|---|
change |
none | Fired after the user picks a day. |
CSS parts
| Part | Description |
|---|---|
header |
The row with the previous and next month buttons and the month title. |
grid |
The <table> holding the weekday headings and days. |
day |
Each day <button>. It carries data-date, plus data-outside for days of the neighbouring months, data-today for today, aria-selected="true" for the selected day and disabled outside min/max. |
::part(day) accepts pseudo-classes such as :hover and :disabled but not attribute selectors, so the
selected, today and outside colours come from the theme tokens (--primary, --accent, --muted-foreground).
Forms
ui-calendar is form-associated. With a name, the form submits value (YYYY-MM-DD), or nothing when no date is
selected. form.reset() restores the value attribute, and the browser's form state restore brings back a
selected date after navigation.
Accessibility
The days are a table with role="grid", and the selected day has aria-selected="true". The month title is an
aria-live="polite" region, so month changes are announced. Only one day is in the tab order at a time (the
selected day, or today when nothing is selected).
| Key | Action |
|---|---|
| Arrow Left / Arrow Right | Move focus one day back or forward. |
| Arrow Up / Arrow Down | Move focus one week back or forward. Moving past the edge of the month shows the next or previous month. |
| Enter / Space | Select the focused day. |
| Tab | Leave the grid. The previous and next month buttons come before it in tab order. |