Minimal calendar shell
Start here when the page only needs the standard month grid.
<CwCalendar
onDateClick={(date) => console.log(date)}
/>Month calendar with bindable month state, snippet-based day rendering, and optional min/max date constraints.
This example shows larger weather and text content, disabled dates outside the allowed range, and the month navigation callback updating a live summary.
Use the component with no snippets when you only need the shared month shell and date-grid layout.
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.
| API | Type | Details |
|---|---|---|
year Default: | number | Displayed year. Bind this when parent state should track the visible month. |
month Default: | number | Displayed month, zero-based like `Date#getMonth()`. |
startOnMonday Default: | boolean | Switches weekday headers between Monday-first and Sunday-first layout. |
minDate | Date | Earliest selectable date and earliest month the user can navigate to. |
maxDate | Date | Latest selectable date and latest month the user can navigate to. |
onDateClick | (date: Date) => void | Runs when the user clicks an enabled day cell. |
onMonthChange | (year: number, month: number, displayedMonth: Date) => void | Runs 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. |
These snippets intentionally show the full public API surface the live demo relies on.
Start here when the page only needs the standard month grid.
<CwCalendar
onDateClick={(date) => console.log(date)}
/>Use this when sibling UI needs to react to visible month changes.
<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>This pattern keeps the calendar shell reusable while the page owns the per-day content.
<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>