CwCalendar

Month calendar with bindable month state, snippet-based day rendering, and optional min/max date constraints.

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.

April 2026
MonTueWedThuFriSatSun
30 Open day
🌤️ 71°
No field work scheduled
31 Open day
69°
No field work scheduled
1 Open day
🌧️ 63°
No field work scheduled
2 Open day
🌦️ 66°
No field work scheduled
3 Open day
☁️ 64°
No field work scheduled
4 Open day
🌤️ 72°
No field work scheduled
5 Sensor calibration
☀️ 74°
Sensor calibration
Field inspection
6 Open day
🌤️ 71°
No field work scheduled
7 Irrigation schedule
69°
Irrigation schedule
Nutrient audit
8 Open day
🌧️ 63°
No field work scheduled
9 Open day
🌦️ 66°
No field work scheduled
10 Harvest window
☁️ 64°
Harvest window
11 Open day
🌤️ 72°
No field work scheduled
12 Open day
☀️ 74°
No field work scheduled
13 Weather alert watch
🌤️ 71°
Weather alert watch
Pump maintenance
14 Open day
69°
No field work scheduled
15 Open day
🌧️ 63°
No field work scheduled
16 Open day
🌦️ 66°
No field work scheduled
17 Open day
☁️ 64°
No field work scheduled
18 Weekly grow review
🌤️ 72°
Weekly grow review
19 Open day
☀️ 74°
No field work scheduled
20 Open day
🌤️ 71°
No field work scheduled
21 Open day
69°
No field work scheduled
22 Open day
🌧️ 63°
No field work scheduled
23 Open day
🌦️ 66°
No field work scheduled
24 Open day
☁️ 64°
No field work scheduled
25 Open day
🌤️ 72°
No field work scheduled
26 Open day
☀️ 74°
No field work scheduled
27 Open day
🌤️ 71°
No field work scheduled
28 Open day
69°
No field work scheduled
29 Open day
🌧️ 63°
No field work scheduled
30 Open day
🌦️ 66°
No field work scheduled
1 Open day
☁️ 64°
No field work scheduled
2 Open day
🌤️ 72°
No field work scheduled
3 Open day
☀️ 74°
No field work scheduled
4 Open day
🌤️ 71°
No field work scheduled
5 Open day
69°
No field work scheduled
6 Open day
🌧️ 63°
No field work scheduled
7 Open day
🌦️ 66°
No field work scheduled
8 Open day
☁️ 64°
No field work scheduled
9 Open day
🌤️ 72°
No field work scheduled
10 Open day
☀️ 74°
No field work scheduled

Minimal month grid

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

April 2026
MonTueWedThuFriSatSun
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
3
4
5
6
7
8
9
10
Documentation Upgrade

Start here

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.

Copy-paste examples

These snippets intentionally show the full public API surface the live demo relies on.

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>