implement
Range Calendar

Range Calendar

A month grid for picking a start and an end date.

Trip dates August 2026
SMTWTFS
26
27
28
29
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
31
1
2
3
4
5
import { signal } from "@implementjs/core";
import { today, type DateRange } from "@implementjs/primitives";
import { RangeCalendar } from "@/lib/components/ui/range-calendar";

export default function RangeCalendarDemo() {
	const value = signal<DateRange>({
		start: today(),
		end: today().add({ days: 4 }),
	});

	return RangeCalendar({ value, calendarLabel: "Trip dates" });
}

Installation

npx jsrepo add @implementjs/ui/range-calendar

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

Usage

Assembled like the calendar — navigation, weekday header, and grid built in — with day styling for the shape of a range: rounded ends, a squared-off band through the middle, and a live highlight over the span being picked before the second click lands.

value is a DateRange: { start, end }, either of which can be null while a selection is in progress.

import { signal } from "@implementjs/core";
import { today, type DateRange } from "@implementjs/primitives";
import { RangeCalendar } from "@/lib/components/ui/range-calendar";

const value = signal<DateRange>({ start: today(), end: today().add({ days: 4 }) });

RangeCalendar({ value, calendarLabel: "Trip dates" });

Two months at a time

numberOfMonths: 2 is the usual shape for a date range picker; the styled root already lays the months out in a row on wider screens and stacks them below md.

RangeCalendar({ value, numberOfMonths: 2, calendarLabel: "Trip dates" });

What it shares with the calendar

The root's border and padding come from calendarRootClasses, exported by calendar.ts — which is why installing this brings the calendar with it. The day classes are its own, since a range needs states a single date has no use for.

The root is assembled, so the header, grid, and cell parts are not re-exported here. Import them from @implementjs/primitives when you want a different structure; Range Calendar covers them.

API Reference

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

RangeCalendar

A range calendar, fully assembled: month navigation, the weekday header, and the day grid are built in, with day styling for the start, middle, and end of a range. Every root prop below is forwarded. Renders a Div; extra props are forwarded onto it.

PropTypeDefaultDescription
valueSignal<DateRange> | DateRange{ start: null, end: null }The selected range. Inverted writes are reordered. Pass a signal to control it from outside.
minDaysnumberThe fewest days a range may span. Shorter picks restart the selection.
maxDaysnumberThe most days a range may span. Longer picks restart the selection.
excludeDisabledbooleanfalseClear the range if it would contain a disabled date.
onRangeSelect() => voidRuns after both ends of the range are selected.
placeholderSignal<CalendarDate> | CalendarDatetodayThe date the view starts on and keyboard focus follows. Pass a signal to control the view from outside.
minValueCalendarDateThe earliest selectable date. Earlier dates are disabled.
maxValueCalendarDateThe latest selectable date. Later dates are disabled.
isDateDisabled(date: CalendarDate) => booleanMarks dates as disabled: not selectable and skipped by the keyboard.
isDateUnavailable(date: CalendarDate) => booleanMarks dates as unavailable: focusable and rendered, but not selectable. Sets data-unavailable.
preventDeselectbooleanfalseWhether clicking a selected date again keeps it selected.
disabledSignal<boolean> | booleanfalseDisables the whole calendar.
readonlySignal<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 | 6The 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-range-calendar-root]Present
[data-invalid]Present when an end is disabled/unavailable or the range is inverted
[data-disabled]Present when disabled
[data-readonly]Present when readonly