CwCalendar

Month calendar with bindable month state, snippet-based day rendering, optional min/max date constraints and a loading state for month navigation. Day cells are keyboard-operable: arrow keys move by day and week, Home/End jump to the start or end of the week, and PageUp/PageDown change month.

Bounded planning calendar

This example shows larger weather and text content, disabled dates outside the allowed range, and the month navigation callback updating a live summary.

October 2026
MonTueWedThuFriSatSun
Open day
🌤️ 71°
No field work scheduled
Open day
⛅ 69°
No field work scheduled
Open day
🌧️ 63°
No field work scheduled
Open day
🌦️ 66°
No field work scheduled
Open day
☁️ 64°
No field work scheduled
Open day
🌤️ 72°
No field work scheduled
Open day
☀️ 74°
No field work scheduled
Open day
🌤️ 71°
No field work scheduled
Open day
⛅ 69°
No field work scheduled
Open day
🌧️ 63°
No field work scheduled
Open day
🌦️ 66°
No field work scheduled
Open day
☁️ 64°
No field work scheduled
Open day
🌤️ 72°
No field work scheduled
Open day
☀️ 74°
No field work scheduled
Open day
🌤️ 71°
No field work scheduled
Open day
⛅ 69°
No field work scheduled
Open day
🌧️ 63°
No field work scheduled
Open day
🌦️ 66°
No field work scheduled
Open day
☁️ 64°
No field work scheduled
Open day
🌤️ 72°
No field work scheduled
Open day
☀️ 74°
No field work scheduled
Open day
🌤️ 71°
No field work scheduled
Open day
⛅ 69°
No field work scheduled
Open day
🌧️ 63°
No field work scheduled
Sensor calibration
🌦️ 66°
Sensor calibration
Field inspection
Open day
☁️ 64°
No field work scheduled
Irrigation schedule
🌤️ 72°
Irrigation schedule
Nutrient audit
Open day
☀️ 74°
No field work scheduled
Open day
🌤️ 71°
No field work scheduled
Harvest window
⛅ 69°
Harvest window
Open day
🌧️ 63°
No field work scheduled
Open day
🌦️ 66°
No field work scheduled
Weather alert watch
☁️ 64°
Weather alert watch
Pump maintenance
Open day
🌤️ 72°
No field work scheduled
Open day
☀️ 74°
No field work scheduled
Open day
🌤️ 71°
No field work scheduled
Open day
⛅ 69°
No field work scheduled
Weekly grow review
🌧️ 63°
Weekly grow review
Open day
🌦️ 66°
No field work scheduled
Open day
☁️ 64°
No field work scheduled
Open day
🌤️ 72°
No field work scheduled
Open day
☀️ 74°
No field work scheduled

Loading and locale

Change month to see the loading state while events are fetched; navigation is paused until loading finishes. Weekday and month names come from locale (here fr-FR).

octobre 2026
lun.mar.mer.jeu.ven.sam.dim.
CwCalendar example Copy code
<CwCalendar
	bind:year
	bind:month
	minDate={minDate}
	maxDate={maxDate}
	loading={isFetchingMonth}
	locale="fr-FR"
	onDateClick={(date) => (selected = date)}
	onMonthChange={(year, month) => fetchMonth(year, month)}
>
	{#snippet dayContent(date)}
		{#each eventsFor(date) as event (event.id)}
			<div class="event">{event.title}</div>
		{/each}
	{/snippet}
</CwCalendar>

Minimal month grid

Use the component with no snippets when you only need the shared month shell and date-grid layout.

October 2026
MonTueWedThuFriSatSun
API

Usage and API reference

CwCalendar is a month grid shell. Use it as a simple calendar first, then layer in `dayHeader`, `dayTrailing`, and `dayContent` snippets only when each day needs richer content.

How to think about it

  1. Decide if the month should be controlled Leave `year` and `month` alone for an internal calendar, or bind them when the rest of the page needs to know which month is on screen.
  2. Constrain with real dates, not custom guards Use `minDate` and `maxDate` to disable both navigation and day clicks outside the allowed window instead of wrapping the callbacks yourself.
  3. Treat snippets as layers `dayHeader` is for the compact label next to the day number, `dayTrailing` is for lightweight metadata like weather, and `dayContent` is the larger body area.

Props and callbacks

APITypeDetails
year

Default: current year

numberDisplayed year. Bind this when parent state should track the visible month.
month

Default: current month

numberDisplayed month, zero-based like `Date#getMonth()`.
startOnMonday

Default: true

booleanSwitches weekday headers between Monday-first and Sunday-first layout.
minDate
DateEarliest selectable date and earliest month the user can navigate to.
maxDate
DateLatest selectable date and latest month the user can navigate to.
onDateClick
(date: Date) => voidRuns when the user clicks an enabled day cell.
onMonthChange
(year: number, month: number, displayedMonth: Date) => voidRuns after the previous or next month buttons change the visible month. `month` is zero-based and `displayedMonth` is the first day of that month.
dayHeader
Snippet<[Date]>Optional compact label rendered beside the day number.
dayTrailing
Snippet<[Date]>Optional metadata rendered on the right side of the day header.
dayContent
Snippet<[Date]>Optional main content block rendered under the header inside each day cell.
locale
stringBCP 47 locale for number and date formatting. Defaults to the runtime locale.
ariaLabel
stringAccessible name for the month grid.
labels
CwCalendar labelsStrings for navigation buttons and the loading state.

Copy-paste examples

Each snippet is a complete, working example you can paste into a Svelte 5 component.

Minimal calendar shell

Start here when the page only needs the standard month grid.

Minimal calendar shell Copy code
<CwCalendar
	onDateClick={(date) => console.log(date)}
/>
Controlled month with min/max bounds

Use this when sibling UI needs to react to visible month changes.

Controlled month with min/max bounds Copy code
<script lang="ts">
	let visibleYear = $state(new Date().getFullYear());
	let visibleMonth = $state(new Date().getMonth());
	let lastMonth = $state(new Date(visibleYear, visibleMonth, 1));

	const minDate = new Date(2026, 2, 10);
	const maxDate = new Date(2026, 4, 22);

	function handleMonthChange(_year: number, _month: number, displayedMonth: Date) {
		lastMonth = displayedMonth;
	}
</script>

<CwCalendar
	bind:year={visibleYear}
	bind:month={visibleMonth}
	{minDate}
	{maxDate}
	onMonthChange={handleMonthChange}
	onDateClick={(date) => console.log(date)}
/>

<p>
	Showing
	{lastMonth.toLocaleDateString(undefined, { month: 'long', year: 'numeric' })}
</p>
Weather and schedule snippets

This pattern keeps the calendar shell reusable while the page owns the per-day content.

Weather and schedule snippets Copy code
<script lang="ts">
	function weatherFor(date: Date) {
		const icons = ['☀️', '🌤️', '⛅', '🌧️', '🌦️', '☁️', '🌤️'];
		return { icon: icons[date.getDay()], high: 70 + (date.getDay() % 4) };
	}

	function jobsFor(date: Date) {
		return date.getDay() === 2
			? ['Irrigation schedule', 'Nutrient audit']
			: [];
	}
</script>

<CwCalendar onDateClick={(date) => console.log(date)}>
	{#snippet dayHeader(date)}
		<span>{jobsFor(date)[0] ?? 'Open day'}</span>
	{/snippet}

	{#snippet dayTrailing(date)}
		{@const weather = weatherFor(date)}
		<div style="display:grid;justify-items:end;gap:0.1rem">
			<span style="font-size:1.2rem">{weather.icon}</span>
			<span>{weather.high}°</span>
		</div>
	{/snippet}

	{#snippet dayContent(date)}
		{#each jobsFor(date) as job}
			<div>{job}</div>
		{/each}
	{/snippet}
</CwCalendar>