Skip to content

Calendar

import { Calendar } from '@delightstack/components';

The CalendarEvent and MarkedDate types are also exported:

import type { CalendarEvent, MarkedDate } from '@delightstack/components';
July 2026
Mon
Tue
Wed
Thu
Fri
Sat
Sun
View code
<script>
import { Calendar } from '@delightstack/components';
let selectedDate = $state<Date | undefined>(undefined);
</script>
<Calendar bind:value={selectedDate} />

The container is transparent by default so it drops onto any surface. Add filled for a subtle surface, or outline for a hairline border — both give it generously rounded card corners. They can be combined.

Default (transparent)
July 2026
Mon
Tue
Wed
Thu
Fri
Sat
Sun
Filled
July 2026
Mon
Tue
Wed
Thu
Fri
Sat
Sun
Outline
July 2026
Mon
Tue
Wed
Thu
Fri
Sat
Sun
View code
<!-- Transparent (default) -->
<Calendar bind:value={date} />
<!-- Subtle surface fill -->
<Calendar bind:value={date} filled />
<!-- Hairline outline -->
<Calendar bind:value={date} outline />

Use dense for compact spacing or comfortable for a roomier layout. The corner radius scales with the density.

Dense
July 2026
Mon
Tue
Wed
Thu
Fri
Sat
Sun
Comfortable
July 2026
Mon
Tue
Wed
Thu
Fri
Sat
Sun
View code
<!-- Compact -->
<Calendar bind:value={date} outline dense />
<!-- Roomy -->
<Calendar bind:value={date} outline comfortable />
July 2026
Mon
Tue
Wed
Thu
Fri
Sat
Sun
View code
<script>
let date = $state<Date | undefined>(undefined);
</script>
<Calendar bind:value={date} />

First click sets the start date, second click sets the end date. Hovering between clicks previews the range.

July 2026
Mon
Tue
Wed
Thu
Fri
Sat
Sun
View code
<script>
let dateRange = $state<[Date, Date] | undefined>(undefined);
</script>
<Calendar mode="range" bind:value={dateRange} />

Click to toggle individual dates on or off.

July 2026
Mon
Tue
Wed
Thu
Fri
Sat
Sun
View code
<script>
let selectedDates = $state<Date[]>([]);
</script>
<Calendar mode="multiple" bind:value={selectedDates} />
July 2026
Mon
Tue
Wed
Thu
Fri
Sat
Sun
View code
<Calendar
bind:value={date}
min={new Date()}
max={new Date(2026, 11, 31)}
/>
March 2026
Mon
Tue
Wed
Thu
Fri
Sat
Sun
View code
<Calendar
bind:value={date}
marked={[
{ date: new Date(2026, 2, 15), color: 'var(--color-action)', label: 'Meeting' },
{ date: new Date(2026, 2, 20), color: 'var(--color-error)', label: 'Deadline' },
]}
/>
March 2026
Mon
Tue
Wed
Thu
Fri
Sat
Sun
View code
<script>
const events = [
{ id: '1', title: 'Team Standup', start: new Date(2026, 2, 12) },
{ id: '2', title: 'Sprint Review', start: new Date(2026, 2, 15), end: new Date(2026, 2, 16), color: '#10b981' },
];
</script>
<Calendar bind:value={date} events={events} />

German (Monday start)

Juli 2026
Mo
Di
Mi
Do
Fr
Sa
So

US English (Sunday start)

July 2026
Sun
Mon
Tue
Wed
Thu
Fri
Sat
View code
<!-- German locale, Monday start -->
<Calendar bind:value={date} locale="de-DE" week_starts_on={1} />
<!-- US locale, Sunday start -->
<Calendar bind:value={date} locale="en-US" week_starts_on={0} />
July 2026
Mon
Tue
Wed
Thu
Fri
Sat
Sun
View code
<Calendar
bind:value={date}
outline
show_time_slots
time_slot_interval={15}
time_slot_min="09:00"
time_slot_max="17:00"
ontimeslotselect={({ time, date }) => console.log(time, date)}
/>
View code
<Calendar skeleton={loading} bind:value={date} />
PropTypeDefaultDescription
valueDate | Date[] | [Date, Date]-Selected date(s), bindable
mode'single' | 'range' | 'multiple''single'Selection mode
monthDatenew Date()Currently displayed month, bindable
minDate-Minimum selectable date
maxDate-Maximum selectable date
disabledDate[] | ((date: Date) => boolean)[]Disabled dates (array or predicate function)
markedMarkedDate[][]Dates with colored dot markers
eventsCalendarEvent[][]Events to display on dates
week_starts_on0 | 1 | 2 | 3 | 4 | 5 | 61First day of week (0=Sunday)
localestring-BCP 47 locale string for formatting
show_time_slotsbooleanfalseShow time slot picker alongside calendar
time_slot_intervalnumber30Time slot interval in minutes
time_slot_minstring'00:00'Earliest time slot
time_slot_maxstring'23:59'Latest time slot
densebooleanfalseCompact cell spacing
comfortablebooleanfalseRelaxed cell spacing
filledbooleanfalseFill the container with a subtle surface (card look)
outlinebooleanfalseAdd a 1px outline + rounded corners (transparent fill)
skeletonbooleanfalseShow loading skeleton
idstringautoElement ID
classstring''Additional CSS classes
EventDetailDescription
onselect{ value: Date | Date[] | [Date, Date] }Selection changed
onmonthchange{ month: Date }Navigated to a different month
ontimeslotselect{ time: string, date: Date }Time slot selected
interface CalendarEvent {
id: string;
title: string;
start: Date;
end?: Date;
color?: string;
allDay?: boolean;
}
interface MarkedDate {
date: Date;
color?: string;
label?: string;
}
  • Calendar container has role="group" with aria-label="Calendar"
  • Day grid has role="grid" with aria-label="Calendar dates"
  • Weekday headers use role="columnheader"
  • Each day cell is a <button> with role="gridcell", aria-selected, and aria-disabled
  • Today and event information included in each cell’s aria-label
  • Month/year header uses aria-live="polite" for screen reader announcements
  • Navigation buttons have aria-label (“Previous month”, “Next month”)
  • Time slots panel has role="listbox" with role="option" on each slot
  • Full keyboard navigation:
    • Arrow keys move focus between days
    • Page Up / Page Down moves by month (with Shift, by year)
    • Home / End moves to start/end of week
    • Enter / Space selects the focused date
  • Focus wraps across month boundaries automatically
  • Respects prefers-reduced-motion