Calendar
A date picker component with calendar interface
Basic Date Selection
Core selection modes with constraints, disabled dates, and locale support.
Simple date selection
| Su | Mo | Tu | We | Th | Fr | Sa |
|---|---|---|---|---|---|---|
1 | 2 | 3 | 4 | 5 | 6 | 7 |
8 | 9 | 10 | 11 | 12 | 13 | 14 |
15 | 16 | 17 | 18 | 19 | 20 | 21 |
22 | 23 | 24 | 25 | 26 | 27 | 28 |
29 | 30 | 31 | 1 | 2 | 3 | 4 |
No date selected
Quick month/year selection
| Su | Mo | Tu | We | Th | Fr | Sa |
|---|---|---|---|---|---|---|
1 | 2 | 3 | 4 | 5 | 6 | 7 |
8 | 9 | 10 | 11 | 12 | 13 | 14 |
15 | 16 | 17 | 18 | 19 | 20 | 21 |
22 | 23 | 24 | 25 | 26 | 27 | 28 |
29 | 30 | 31 | 1 | 2 | 3 | 4 |
No date selected
Pre-selected date
| Su | Mo | Tu | We | Th | Fr | Sa |
|---|---|---|---|---|---|---|
1 | 2 | 3 | 4 | 5 | 6 | 7 |
8 | 9 | 10 | 11 | 12 | 13 | 14 |
15 | 16 | 17 | 18 | 19 | 20 | 21 |
22 | 23 | 24 | 25 | 26 | 27 | 28 |
29 | 30 | 31 | 1 | 2 | 3 | 4 |
Initialized with today's date
Min/max date restrictions
| Su | Mo | Tu | We | Th | Fr | Sa |
|---|---|---|---|---|---|---|
1 | 2 | 3 | 4 | 5 | 6 | 7 |
8 | 9 | 10 | 11 | 12 | 13 | 14 |
15 | 16 | 17 | 18 | 19 | 20 | 21 |
22 | 23 | 24 | 25 | 26 | 27 | 28 |
29 | 30 | 31 | 1 | 2 | 3 | 4 |
Today to 2 months ahead
Weekends are not selectable
| Su | Mo | Tu | We | Th | Fr | Sa |
|---|---|---|---|---|---|---|
1 | 2 | 3 | 4 | 5 | 6 | 7 |
8 | 9 | 10 | 11 | 12 | 13 | 14 |
15 | 16 | 17 | 18 | 19 | 20 | 21 |
22 | 23 | 24 | 25 | 26 | 27 | 28 |
29 | 30 | 31 | 1 | 2 | 3 | 4 |
Saturday and Sunday disabled
Dates marked as unavailable
| Su | Mo | Tu | We | Th | Fr | Sa |
|---|---|---|---|---|---|---|
1 | 2 | 3 | 4 | 5 | 6 | 7 |
8 | 9 | 10 | 11 | 12 | 13 | 14 |
15 | 16 | 17 | 18 | 19 | 20 | 21 |
22 | 23 | 24 | 25 | 26 | 27 | 28 |
29 | 30 | 31 | 1 | 2 | 3 | 4 |
5th, 10th, 15th, 20th, 25th strikethrough
European style calendar
| Mo | Tu | We | Th | Fr | Sa | Su |
|---|---|---|---|---|---|---|
23 | 24 | 25 | 26 | 27 | 28 | 1 |
2 | 3 | 4 | 5 | 6 | 7 | 8 |
9 | 10 | 11 | 12 | 13 | 14 | 15 |
16 | 17 | 18 | 19 | 20 | 21 | 22 |
23 | 24 | 25 | 26 | 27 | 28 | 29 |
30 | 31 | 1 | 2 | 3 | 4 | 5 |
Week begins on Monday
Always shows 6 weeks
| Su | Mo | Tu | We | Th | Fr | Sa |
|---|---|---|---|---|---|---|
1 | 2 | 3 | 4 | 5 | 6 | 7 |
8 | 9 | 10 | 11 | 12 | 13 | 14 |
15 | 16 | 17 | 18 | 19 | 20 | 21 |
22 | 23 | 24 | 25 | 26 | 27 | 28 |
29 | 30 | 31 | 1 | 2 | 3 | 4 |
5 | 6 | 7 | 8 | 9 | 10 | 11 |
Consistent height across months
Localized to Spanish
| lu | ma | mi | ju | vi | sá | do |
|---|---|---|---|---|---|---|
23 | 24 | 25 | 26 | 27 | 28 | 1 |
2 | 3 | 4 | 5 | 6 | 7 | 8 |
9 | 10 | 11 | 12 | 13 | 14 | 15 |
16 | 17 | 18 | 19 | 20 | 21 | 22 |
23 | 24 | 25 | 26 | 27 | 28 | 29 |
30 | 31 | 1 | 2 | 3 | 4 | 5 |
Ninguna fecha seleccionada
1
2<script lang="ts">
3 import { Calendar } from "@kareyes/aether";
4 import { today, getLocalTimeZone, isWeekend, type DateValue } from "@internationalized/date";
5
6 let selected = $state<DateValue | undefined>();
7</script>
8
9<!-- Basic single date selection -->
10<Calendar type="single" bind:value={selected} />
11
12<!-- Dropdown navigation (month + year selects) -->
13<Calendar type="single" bind:value={selected} captionLayout="dropdown" />
14
15<!-- Pre-selected with today's date -->
16<Calendar type="single" value={today(getLocalTimeZone())} />
17
18<!-- Only allow dates from today to 2 months ahead -->
19<Calendar
20 type="single"
21 bind:value={selected}
22 minValue={today(getLocalTimeZone())}
23 maxValue={today(getLocalTimeZone()).add({ months: 2 })}
24/>
25
26<!-- Disable weekends -->
27<Calendar
28 type="single"
29 isDateDisabled={(date: DateValue) => isWeekend(date, "en-US")}
30/>
31
32<!-- Mark specific days as unavailable -->
33<Calendar
34 type="single"
35 isDateUnavailable={(date: DateValue) => [5, 10, 15, 20, 25].includes(date.day)}
36/>
37
38<!-- Week starts on Monday -->
39<Calendar type="single" weekStartsOn={1} />
40
41<!-- Always show 6 weeks (fixed height) -->
42<Calendar type="single" fixedWeeks={true} />
43
44<!-- Spanish locale -->
45<Calendar type="single" locale="es-ES" />Multiple Months
Display multiple months side by side using numberOfMonths.
Display two months side by side
| Su | Mo | Tu | We | Th | Fr | Sa |
|---|---|---|---|---|---|---|
1 | 2 | 3 | 4 | 5 | 6 | 7 |
8 | 9 | 10 | 11 | 12 | 13 | 14 |
15 | 16 | 17 | 18 | 19 | 20 | 21 |
22 | 23 | 24 | 25 | 26 | 27 | 28 |
29 | 30 | 31 | 1 | 2 | 3 | 4 |
| Su | Mo | Tu | We | Th | Fr | Sa |
|---|---|---|---|---|---|---|
29 | 30 | 31 | 1 | 2 | 3 | 4 |
5 | 6 | 7 | 8 | 9 | 10 | 11 |
12 | 13 | 14 | 15 | 16 | 17 | 18 |
19 | 20 | 21 | 22 | 23 | 24 | 25 |
26 | 27 | 28 | 29 | 30 | 1 | 2 |
No date selected
1
2<script lang="ts">
3 import { Calendar } from "@kareyes/aether";
4 import type { DateValue } from "@internationalized/date";
5
6 let selected = $state<DateValue | undefined>();
7</script>
8
9<Calendar type="single" bind:value={selected} numberOfMonths={2} />Caption Layouts
Control how the month/year header is rendered with captionLayout.
Default caption style
| Su | Mo | Tu | We | Th | Fr | Sa |
|---|---|---|---|---|---|---|
1 | 2 | 3 | 4 | 5 | 6 | 7 |
8 | 9 | 10 | 11 | 12 | 13 | 14 |
15 | 16 | 17 | 18 | 19 | 20 | 21 |
22 | 23 | 24 | 25 | 26 | 27 | 28 |
29 | 30 | 31 | 1 | 2 | 3 | 4 |
Month & year dropdowns
| Su | Mo | Tu | We | Th | Fr | Sa |
|---|---|---|---|---|---|---|
1 | 2 | 3 | 4 | 5 | 6 | 7 |
8 | 9 | 10 | 11 | 12 | 13 | 14 |
15 | 16 | 17 | 18 | 19 | 20 | 21 |
22 | 23 | 24 | 25 | 26 | 27 | 28 |
29 | 30 | 31 | 1 | 2 | 3 | 4 |
Month dropdown only
| Su | Mo | Tu | We | Th | Fr | Sa |
|---|---|---|---|---|---|---|
1 | 2 | 3 | 4 | 5 | 6 | 7 |
8 | 9 | 10 | 11 | 12 | 13 | 14 |
15 | 16 | 17 | 18 | 19 | 20 | 21 |
22 | 23 | 24 | 25 | 26 | 27 | 28 |
29 | 30 | 31 | 1 | 2 | 3 | 4 |
Year dropdown only
| Su | Mo | Tu | We | Th | Fr | Sa |
|---|---|---|---|---|---|---|
1 | 2 | 3 | 4 | 5 | 6 | 7 |
8 | 9 | 10 | 11 | 12 | 13 | 14 |
15 | 16 | 17 | 18 | 19 | 20 | 21 |
22 | 23 | 24 | 25 | 26 | 27 | 28 |
29 | 30 | 31 | 1 | 2 | 3 | 4 |
1
2<script lang="ts">
3 import { Calendar } from "@kareyes/aether";
4</script>
5
6<!-- Default label (prev/next arrows only) -->
7<Calendar type="single" captionLayout="label" />
8
9<!-- Both month and year dropdowns -->
10<Calendar type="single" captionLayout="dropdown" />
11
12<!-- Month dropdown only -->
13<Calendar type="single" captionLayout="dropdown-months" />
14
15<!-- Year dropdown only -->
16<Calendar type="single" captionLayout="dropdown-years" />Navigation Button Variants
Style the prev/next navigation buttons with buttonVariant.
Default navigation style
| Su | Mo | Tu | We | Th | Fr | Sa |
|---|---|---|---|---|---|---|
1 | 2 | 3 | 4 | 5 | 6 | 7 |
8 | 9 | 10 | 11 | 12 | 13 | 14 |
15 | 16 | 17 | 18 | 19 | 20 | 21 |
22 | 23 | 24 | 25 | 26 | 27 | 28 |
29 | 30 | 31 | 1 | 2 | 3 | 4 |
Outlined navigation buttons
| Su | Mo | Tu | We | Th | Fr | Sa |
|---|---|---|---|---|---|---|
1 | 2 | 3 | 4 | 5 | 6 | 7 |
8 | 9 | 10 | 11 | 12 | 13 | 14 |
15 | 16 | 17 | 18 | 19 | 20 | 21 |
22 | 23 | 24 | 25 | 26 | 27 | 28 |
29 | 30 | 31 | 1 | 2 | 3 | 4 |
Solid navigation buttons
| Su | Mo | Tu | We | Th | Fr | Sa |
|---|---|---|---|---|---|---|
1 | 2 | 3 | 4 | 5 | 6 | 7 |
8 | 9 | 10 | 11 | 12 | 13 | 14 |
15 | 16 | 17 | 18 | 19 | 20 | 21 |
22 | 23 | 24 | 25 | 26 | 27 | 28 |
29 | 30 | 31 | 1 | 2 | 3 | 4 |
1
2<script lang="ts">
3 import { Calendar } from "@kareyes/aether";
4</script>
5
6<!-- Ghost (default) -->
7<Calendar type="single" buttonVariant="ghost" />
8
9<!-- Outline -->
10<Calendar type="single" buttonVariant="outline" />
11
12<!-- Solid -->
13<Calendar type="single" buttonVariant="default" />States
Disable all interaction or allow navigation without selection.
Calendar is fully disabled
| Su | Mo | Tu | We | Th | Fr | Sa |
|---|---|---|---|---|---|---|
1 | 2 | 3 | 4 | 5 | 6 | 7 |
8 | 9 | 10 | 11 | 12 | 13 | 14 |
15 | 16 | 17 | 18 | 19 | 20 | 21 |
22 | 23 | 24 | 25 | 26 | 27 | 28 |
29 | 30 | 31 | 1 | 2 | 3 | 4 |
Can navigate but not select
| Su | Mo | Tu | We | Th | Fr | Sa |
|---|---|---|---|---|---|---|
1 | 2 | 3 | 4 | 5 | 6 | 7 |
8 | 9 | 10 | 11 | 12 | 13 | 14 |
15 | 16 | 17 | 18 | 19 | 20 | 21 |
22 | 23 | 24 | 25 | 26 | 27 | 28 |
29 | 30 | 31 | 1 | 2 | 3 | 4 |
1
2<script lang="ts">
3 import { Calendar } from "@kareyes/aether";
4 import { today, getLocalTimeZone } from "@internationalized/date";
5</script>
6
7<!-- Fully disabled — no interaction -->
8<Calendar type="single" disabled />
9
10<!-- Readonly — navigate but cannot select -->
11<Calendar type="single" readonly value={today(getLocalTimeZone())} />Size Variants
Four sizes to match different layout needs.
Compact calendar size
| Su | Mo | Tu | We | Th | Fr | Sa |
|---|---|---|---|---|---|---|
1 | 2 | 3 | 4 | 5 | 6 | 7 |
8 | 9 | 10 | 11 | 12 | 13 | 14 |
15 | 16 | 17 | 18 | 19 | 20 | 21 |
22 | 23 | 24 | 25 | 26 | 27 | 28 |
29 | 30 | 31 | 1 | 2 | 3 | 4 |
Standard calendar size
| Su | Mo | Tu | We | Th | Fr | Sa |
|---|---|---|---|---|---|---|
1 | 2 | 3 | 4 | 5 | 6 | 7 |
8 | 9 | 10 | 11 | 12 | 13 | 14 |
15 | 16 | 17 | 18 | 19 | 20 | 21 |
22 | 23 | 24 | 25 | 26 | 27 | 28 |
29 | 30 | 31 | 1 | 2 | 3 | 4 |
Larger calendar cells
| Su | Mo | Tu | We | Th | Fr | Sa |
|---|---|---|---|---|---|---|
1 | 2 | 3 | 4 | 5 | 6 | 7 |
8 | 9 | 10 | 11 | 12 | 13 | 14 |
15 | 16 | 17 | 18 | 19 | 20 | 21 |
22 | 23 | 24 | 25 | 26 | 27 | 28 |
29 | 30 | 31 | 1 | 2 | 3 | 4 |
Maximum calendar size
| Su | Mo | Tu | We | Th | Fr | Sa |
|---|---|---|---|---|---|---|
1 | 2 | 3 | 4 | 5 | 6 | 7 |
8 | 9 | 10 | 11 | 12 | 13 | 14 |
15 | 16 | 17 | 18 | 19 | 20 | 21 |
22 | 23 | 24 | 25 | 26 | 27 | 28 |
29 | 30 | 31 | 1 | 2 | 3 | 4 |
1
2<script lang="ts">
3 import { Calendar } from "@kareyes/aether";
4</script>
5
6<Calendar type="single" size="sm" />
7<Calendar type="single" size="default" />
8<Calendar type="single" size="lg" />
9<Calendar type="single" size="xl" />Event Markers
Dots on smaller sizes, badge labels on larger sizes. Check dates 5, 10, 15, 20, and today.
Dots display for events
| Su | Mo | Tu | We | Th | Fr | Sa |
|---|---|---|---|---|---|---|
1 | 2 | 3 | 4 | 5 | 6 | 7 |
8 | 9 | 10 | 11 | 12 | 13 | 14 |
15 +1 | 16 | 17 | 18 | 19 | 20 | 21 |
22 | 23 | 24 | 25 | 26 | 27 | 28 |
29 | 30 | 31 | 1 | 2 | 3 | 4 |
Dots display for events
| Su | Mo | Tu | We | Th | Fr | Sa |
|---|---|---|---|---|---|---|
1 | 2 | 3 | 4 | 5 | 6 | 7 |
8 | 9 | 10 | 11 | 12 | 13 | 14 |
15 | 16 | 17 | 18 | 19 | 20 | 21 |
22 | 23 | 24 | 25 | 26 | 27 | 28 |
29 | 30 | 31 | 1 | 2 | 3 | 4 |
Badge display for events
| Su | Mo | Tu | We | Th | Fr | Sa |
|---|---|---|---|---|---|---|
1 | 2 | 3 | 4 | 5 | 6 | 7 |
8 | 9 | 10 | 11 | 12 | 13 | 14 |
15 | 16 | 17 | 18 | 19 | 20 | 21 |
22 | 23 | 24 | 25 | 26 | 27 | 28 |
29 | 30 | 31 | 1 | 2 | 3 | 4 |
Full badge labels
| Su | Mo | Tu | We | Th | Fr | Sa |
|---|---|---|---|---|---|---|
1 | 2 | 3 | 4 | 5 | 6 | 7 |
8 | 9 | 10 | 11 | 12 | 13 | 14 |
15 | 16 | 17 | 18 | 19 | 20 | 21 |
22 | 23 | 24 | 25 | 26 | 27 | 28 |
29 | 30 | 31 | 1 | 2 | 3 | 4 |
1
2<script lang="ts">
3 import { Calendar, type CalendarPrimitives } from "@kareyes/aether";
4 import { today, getLocalTimeZone } from "@internationalized/date";
5
6 type CalendarEvent = CalendarPrimitives.CalendarEvent;
7
8 const now = today(getLocalTimeZone());
9 const pad = (n: number) => String(n).padStart(2, "0");
10
11 const events: CalendarEvent[] = [
12 { date: `${now.year}-${pad(now.month)}-05`, color: "#ef4444", label: "Meeting" },
13 { date: `${now.year}-${pad(now.month)}-05`, color: "#3b82f6", label: "Call" },
14 { date: `${now.year}-${pad(now.month)}-10`, color: "#22c55e", label: "Event" },
15 { date: `${now.year}-${pad(now.month)}-15`, color: "#f59e0b", label: "Deadline" },
16 { date: `${now.year}-${pad(now.month)}-20`, color: "#06b6d4", label: "Launch" },
17 ];
18</script>
19
20<!-- Dots on sm/default, badge labels on lg/xl -->
21<Calendar type="single" size="sm" {events} />
22<Calendar type="single" size="default" {events} />
23<Calendar type="single" size="lg" {events} />
24<Calendar type="single" size="xl" {events} />Combined Features
Mixing size, events, caption layout, and fixed weeks together.
Full-featured large calendar
| Su | Mo | Tu | We | Th | Fr | Sa |
|---|---|---|---|---|---|---|
1 | 2 | 3 | 4 | 5 | 6 | 7 |
8 | 9 | 10 | 11 | 12 | 13 | 14 |
15 | 16 | 17 | 18 | 19 | 20 | 21 |
22 | 23 | 24 | 25 | 26 | 27 | 28 |
29 | 30 | 31 | 1 | 2 | 3 | 4 |
5 | 6 | 7 | 8 | 9 | 10 | 11 |
1
2<script lang="ts">
3 import { Calendar, type CalendarPrimitives } from "@kareyes/aether";
4 import { today, getLocalTimeZone } from "@internationalized/date";
5
6 type CalendarEvent = CalendarPrimitives.CalendarEvent;
7
8 const now = today(getLocalTimeZone());
9 const pad = (n: number) => String(n).padStart(2, "0");
10
11 const events: CalendarEvent[] = [
12 { date: `${now.year}-${pad(now.month)}-10`, color: "#22c55e", label: "Event" },
13 { date: `${now.year}-${pad(now.month)}-15`, color: "#f59e0b", label: "Deadline" },
14 ];
15</script>
16
17<Calendar
18 type="single"
19 size="xl"
20 {events}
21 captionLayout="dropdown"
22 fixedWeeks={true}
23/>Full Size Calendar
Full-width view with event cards. Events display as colored cards with labels. Responsive on mobile/desktop.
| Sunday Su | Monday Mo | Tuesday Tu | Wednesday We | Thursday Th | Friday Fr | Saturday Sa |
|---|---|---|---|---|---|---|
1 | 2 | 3 | 4 | 5 | 6 | 7 |
8 | 9 | 10 | 11 | 12 | 13 | 14 |
15 | 16 | 17 | 18 +1 | 19 | 20 | 21 |
22 | 23 | 24 | 25 | 26 | 27 | 28 |
29 | 30 | 31 | 1 | 2 | 3 | 4 |
5 | 6 | 7 | 8 | 9 | 10 | 11 |
1
2<script lang="ts">
3 import { Calendar, type CalendarPrimitives } from "@kareyes/aether";
4 import { today, getLocalTimeZone } from "@internationalized/date";
5
6 type CalendarEvent = CalendarPrimitives.CalendarEvent;
7
8 const now = today(getLocalTimeZone());
9 const pad = (n: number) => String(n).padStart(2, "0");
10
11 const events: CalendarEvent[] = [
12 { date: `${now.year}-${pad(now.month)}-03`, color: "#3b82f6", label: "Team Standup", description: "Daily sync" },
13 { date: `${now.year}-${pad(now.month)}-05`, color: "#ef4444", label: "Client Meeting" },
14 { date: `${now.year}-${pad(now.month)}-15`, color: "#ec4899", label: "Release v2.0", description: "Major deployment" },
15 { date: `${now.year}-${pad(now.month)}-20`, color: "#06b6d4", label: "Conference" },
16 ];
17</script>
18
19<div class="border rounded-lg overflow-hidden bg-background">
20 <Calendar type="single" size="full" {events} fixedWeeks={true} />
21</div>Basic Usage
<script>
import { Calendar } from "@kareyes/aether";
import type { DateValue } from "@internationalized/date";
let value = $state<DateValue | undefined>();
</script>
<Calendar bind:value={value} />
Props
| Prop | Type | Default | Description |
|---|---|---|---|
value |
DateValue | DateValue[] |
undefined |
The selected date(s) (bindable) |
placeholder |
DateValue |
undefined |
Placeholder date for initial calendar view (bindable) |
weekdayFormat |
"narrow" | "short" | "long" |
"short" |
Format for weekday headers |
captionLayout |
"label" | "dropdown" | "dropdown-months" | "dropdown-years" |
"label" |
Navigation caption style |
buttonVariant |
ButtonVariant |
"ghost" |
Variant for prev/next navigation buttons |
locale |
string |
"en-US" |
Locale for date formatting |
months |
Month[] |
undefined |
Custom months for dropdown (when using captionLayout dropdown) |
years |
Year[] |
undefined |
Custom years for dropdown |
monthFormat |
"numeric" | "2-digit" | "narrow" | "short" | "long" | function |
Auto | Month display format |
yearFormat |
"numeric" | "2-digit" | function |
"numeric" |
Year display format |
numberOfMonths |
number |
1 |
Number of months to display |
pagedNavigation |
boolean |
false |
Navigate by number of months displayed |
preventDeselect |
boolean |
false |
Prevent deselecting the current value |
weekStartsOn |
0-6 |
0 |
Day the week starts on (0 = Sunday) |
fixedWeeks |
boolean |
false |
Always show 6 weeks |
disabled |
boolean |
false |
Disable the calendar |
readonly |
boolean |
false |
Make the calendar read-only |
minValue |
DateValue |
undefined |
Minimum selectable date |
maxValue |
DateValue |
undefined |
Maximum selectable date |
isDateDisabled |
(date: DateValue) => boolean |
undefined |
Function to disable specific dates |
isDateUnavailable |
(date: DateValue) => boolean |
undefined |
Function to mark dates as unavailable |
disableDaysOutsideMonth |
boolean |
false |
Disable days outside the current month |
day |
Snippet |
undefined |
Custom snippet for rendering day cells |
size |
"sm" | "default" | "lg" | "xl" |
"default" |
Calendar size variant |
events |
CalendarEvent[] |
[] |
Array of events to display as markers |
class |
string |
undefined |
Additional CSS classes |
Size Variants
The calendar supports four size variants.
Small
<Calendar size="sm" />
Default
<Calendar size="default" />
Large
<Calendar size="lg" />
Extra Large
<Calendar size="xl" />
Full (Responsive)
Full-width calendar with event cards. Perfect for scheduling applications.
<Calendar size="full" fixedWeeks={true} {events} />
Features of full size:
- Full-width responsive layout
- Events display as colored cards with labels
- Cells expand to show multiple events
- Header with weekday names in primary color
- Mobile responsive (smaller cells on mobile)
Event Markers
Display event indicators on specific dates. Events show as dots on smaller sizes (sm, default) and badges on larger sizes (lg, xl).
CalendarEvent Type
type CalendarEvent = {
date: string; // ISO date string (YYYY-MM-DD)
color?: string; // Optional color (CSS color value)
label?: string; // Optional label (shown in badge on lg/xl)
};
Basic Events
<script>
import { Calendar, type CalendarEvent } from "@kareyes/aether";
const events: CalendarEvent[] = [
{ date: "2024-12-05", color: "#ef4444", label: "Meeting" },
{ date: "2024-12-10", color: "#22c55e", label: "Event" },
{ date: "2024-12-15", color: "#3b82f6" },
];
</script>
<Calendar {events} />
Events with Different Sizes
<!-- Dots display (small/default sizes) -->
<Calendar size="sm" {events} />
<Calendar size="default" {events} />
<!-- Badge display (large/xl sizes) -->
<Calendar size="lg" {events} />
<Calendar size="xl" {events} />
Multiple Events on Same Date
When multiple events fall on the same date:
- Small/Default sizes: Shows multiple colored dots (up to 2-3 based on size)
- Large/XL sizes: Shows event count badge when multiple events
<script>
const events: CalendarEvent[] = [
{ date: "2024-12-15", color: "#ef4444", label: "Meeting" },
{ date: "2024-12-15", color: "#3b82f6", label: "Call" },
{ date: "2024-12-15", color: "#22c55e" },
];
</script>
<!-- Shows 3 dots on default, shows "3" badge on xl -->
<Calendar size="default" {events} />
<Calendar size="xl" {events} />
Caption Layouts
Label (Default)
Shows month and year as plain text.
<Calendar captionLayout="label" />
Dropdown
Shows both month and year as dropdowns for quick navigation.
<Calendar captionLayout="dropdown" />
Dropdown Months
Shows month as dropdown, year as text.
<Calendar captionLayout="dropdown-months" />
Dropdown Years
Shows month as text, year as dropdown.
<Calendar captionLayout="dropdown-years" />
Multiple Months
Display multiple months side by side.
<Calendar numberOfMonths={2} />
Date Constraints
Min/Max Dates
<script>
import { Calendar } from "@kareyes/aether";
import { today, getLocalTimeZone } from "@internationalized/date";
const now = today(getLocalTimeZone());
const minDate = now;
const maxDate = now.add({ months: 3 });
</script>
<Calendar minValue={minDate} maxValue={maxDate} />
Disable Specific Dates
<script>
import { Calendar } from "@kareyes/aether";
import { isWeekend } from "@internationalized/date";
// Disable weekends
const isDateDisabled = (date) => isWeekend(date, "en-US");
</script>
<Calendar {isDateDisabled} />
Unavailable Dates
<script>
import { Calendar } from "@kareyes/aether";
// Mark certain dates as unavailable (shown with strikethrough)
const bookedDates = [5, 10, 15, 20];
const isDateUnavailable = (date) => bookedDates.includes(date.day);
</script>
<Calendar {isDateUnavailable} />
Custom Day Rendering
Use the day snippet to customize how each day cell is rendered.
<script>
import { Calendar } from "@kareyes/aether";
import * as CalendarPrimitives from "@kareyes/aether";
const events = {
5: "Meeting",
12: "Birthday",
20: "Deadline"
};
</script>
<Calendar>
{#snippet day({ day, outsideMonth })}
<CalendarPrimitives.Day>
<span>{day.day}</span>
{#if events[day.day] && !outsideMonth}
<span class="text-[10px] text-primary">*</span>
{/if}
</CalendarPrimitives.Day>
{/snippet}
</Calendar>
Custom Year Range
Provide a specific range of years for the dropdown.
<script>
import { Calendar } from "@kareyes/aether";
// Generate years from 2020 to 2030
const years = Array.from({ length: 11 }, (_, i) => ({
value: 2020 + i,
label: String(2020 + i)
}));
</script>
<Calendar captionLayout="dropdown" {years} />
Locale Support
<Calendar locale="es-ES" />
<Calendar locale="fr-FR" />
<Calendar locale="de-DE" />
<Calendar locale="ja-JP" />
Week Starts On
<!-- Week starts on Monday -->
<Calendar weekStartsOn={1} />
<!-- Week starts on Saturday -->
<Calendar weekStartsOn={6} />
Working with Dates
The Calendar uses @internationalized/date for date handling:
<script>
import { Calendar } from "@kareyes/aether";
import {
CalendarDate,
today,
getLocalTimeZone,
parseDate
} from "@internationalized/date";
let value = $state();
// Today's date
value = today(getLocalTimeZone());
// Specific date
value = new CalendarDate(2024, 12, 25);
// Parse from string
value = parseDate("2024-06-15");
// Convert to JavaScript Date
const jsDate = value?.toDate(getLocalTimeZone());
</script>
<Calendar bind:value={value} />
Styling
The Calendar uses CSS variables for theming and sizing:
/* Size variants use these CSS variables: */
.calendar {
/* sm: */
--cell-size: var(--spacing-7); /* 1.75rem */
--cell-text: 0.75rem;
--head-text: 0.7rem;
/* default: */
--cell-size: var(--spacing-8); /* 2rem */
--cell-text: 0.875rem;
--head-text: 0.8rem;
/* lg: */
--cell-size: var(--spacing-10); /* 2.5rem */
--cell-text: 0.875rem;
--head-text: 0.8rem;
/* xl: */
--cell-size: var(--spacing-12); /* 3rem */
--cell-text: 1rem;
--head-text: 0.875rem;
/* full: */
/* Cells are flex-based, expand to fill width */
--cell-text: 0.875rem;
--head-text: 0.875rem;
}
Button Variants for Navigation
<Calendar buttonVariant="default" />
<Calendar buttonVariant="outline" />
<Calendar buttonVariant="ghost" />
<Calendar buttonVariant="secondary" />
Primitives
For advanced customization, use the Calendar primitives directly:
<script>
import * as CalendarPrimitives from "@kareyes/aether";
</script>
<CalendarPrimitives.Calendar>
<!-- Custom implementation -->
</CalendarPrimitives.Calendar>
Available primitives:
Calendar- Root componentMonths- Container for monthsMonth- Single month containerHeader- Month headerCaption- Month/year captionNav- Navigation containerPrevButton- Previous month buttonNextButton- Next month buttonGrid- Calendar gridGridHead- Grid header (weekdays)GridBody- Grid body (dates)GridRow- Grid rowHeadCell- Weekday header cellCell- Date cell containerDay- Day buttonEventMarker- Event marker (dots/badge)MonthSelect- Month dropdownYearSelect- Year dropdown
Features
- Single date selection
- Multiple months display
- Dropdown navigation for month/year
- Custom day rendering with snippets
- Date constraints (min/max, disabled, unavailable)
- Locale support
- Week start customization
- Fixed weeks option
- Keyboard navigation
- Full accessibility support
- Dark mode support
- Size variants (sm, default, lg, xl)
- Event markers (dots for small sizes, badges for large sizes)
Accessibility
- Full keyboard navigation (Arrow keys, Home, End, Page Up/Down)
- ARIA labels and roles
- Focus management
- Screen reader announcements for date changes
Dependencies
bits-ui- Headless UI primitives@internationalized/date- Date handling@lucide/svelte- Icons (for navigation buttons)- Tailwind CSS - Styling