implement
Calendar

Calendar

A month grid for picking a single date, or several.

Appointment date September 2026
SMTWTFS
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
import { signal } from "@implementjs/core";
import { today, type CalendarDate } from "@implementjs/primitives";
import { Calendar } from "@/lib/components/ui/calendar";

export default function CalendarDemo() {
	const value = signal<CalendarDate | null>(today());

	return Calendar({ value, calendarLabel: "Appointment date" });
}

Installation

npx jsrepo add @implementjs/ui/calendar

jsrepo pulls button along with it, and installs @implementjs/lucide.

Usage

The styled calendar is assembled, not composed: Calendar renders the navigation, the weekday header, and the day grid itself, so a whole date picker is one call. Every root prop from the primitive is forwarded — value, type, minValue, isDateUnavailable, numberOfMonths, and the rest.

import { signal } from "@implementjs/core";
import { today, type CalendarDate } from "@implementjs/primitives";
import { Calendar } from "@/lib/components/ui/calendar";

const value = signal<CalendarDate | null>(today());

Calendar({ value, calendarLabel: "Appointment date" });

Multiple dates

type: "multiple" turns value into a Signal<CalendarDate[]>:

const dates = signal<CalendarDate[]>([]);

Calendar({ type: "multiple", value: dates, calendarLabel: "Available days" });

Building your own layout

The file exports its pieces so you do not have to fork it to rearrange them. CalendarNav() is the header row, and CalendarMonthGrid(month, weekdays) is one month, weekday header included — its trailing Cell and Day arguments swap in parts of your own.

Drop down to the primitive's own parts when you want a different structure entirely; Calendar covers them.

API Reference

Every prop the styling does not consume is forwarded to the Calendar primitive, so the tables below are the whole surface — the behavior props and the styling ones together.

Calendar

A calendar, fully assembled: month navigation, the weekday header, and the day grid are built in, so the styled root takes children of its own only through the primitive's render function. Every root prop below is forwarded. Renders a Div; extra props are forwarded onto it.

PropTypeDefaultDescription
type"single" | "multiple""single"Whether one date is selected, or several can be.
valueSignal<CalendarDate | null> | Signal<CalendarDate[]>—The selection. CalendarDate | null when type is "single", CalendarDate[] when "multiple". Pass a signal to control it from outside.
onValueChange(value: CalendarDate | null) => void (single) | (value: CalendarDate[]) => void (multiple)—Runs whenever the selection changes, including a deselection.
maxDaysnumber—Only for type "multiple": the most dates that can be selected. Exceeding it restarts the selection at the clicked date.
onDateSelect() => void—Runs after a date is selected (not after a deselection).
placeholderSignal<CalendarDate> | CalendarDatetodayThe date the view starts on and keyboard focus follows. Pass a signal to control the view from outside.
minValueCalendarDate—The earliest selectable date. Earlier dates are disabled.
maxValueCalendarDate—The latest selectable date. Later dates are disabled.
isDateDisabled(date: CalendarDate) => boolean—Marks dates as disabled: not selectable and skipped by the keyboard.
isDateUnavailable(date: CalendarDate) => boolean—Marks dates as unavailable: focusable and rendered, but not selectable. Sets data-unavailable.
preventDeselectbooleanfalseWhether clicking a selected date again keeps it selected.
disabledReadable<boolean> | booleanfalseDisables the whole calendar.
readonlyReadable<boolean> | booleanfalseThe value can be read but not changed.
fixedWeeksbooleanfalseAlways render six weeks per month so the grid height never changes.
numberOfMonthsnumber1How many consecutive months are rendered.
pagedNavigationbooleanfalseWhether prev/next move by numberOfMonths months instead of one.
weekStartsOn0 | 1 | 2 | 3 | 4 | 5 | 6—The day the week starts on, 0 being Sunday. Defaults to the locale's week start where the runtime knows it.
weekdayFormat"narrow" | "short" | "long""narrow"The Intl width of the weekday names handed to the render function.
disableDaysOutsideMonthbooleantrueWhether the leading and trailing days outside the month are disabled.
localestring"en-US"BCP 47 locale tag used for all formatting.
calendarLabelstring"Event"Prefixed onto the visible month to label the calendar for assistive technology.
monthFormatIntl.DateTimeFormatOptions["month"] | ((month: number) => string)"long"How the heading and month select format month names.
yearFormatIntl.DateTimeFormatOptions["year"] | ((year: number) => string)"numeric"How the heading and year select format years.
Data attributeValue
[data-calendar-root]Present
[data-invalid]Present when the value is disabled or unavailable
[data-disabled]Present when disabled
[data-readonly]Present when readonly

CalendarNav

The header row the root renders — previous button, month heading, next button. Exported so a custom layout can reuse it. Takes no arguments.

CalendarMonthGrid

One month's grid, weekday header included: CalendarMonthGrid(month, weekdays, Cell?, Day?). The trailing arguments swap in cell and day parts of your own.