// preview in
saved for every page

Range calendar

Shows a month and lets people pick a start and an end day in two clicks. Use it for stays, trips, time off and report periods.

Built on React AriaPattern WAI-ARIA GridClient componentSource ↗
minimal · light · #3fa34d

Trip dates, October 2026

27
28
29
30
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
weekdayStyle
firstDayOfWeek
allowsNonContiguousRanges
readOnly
disabled

Installation

Set up Camaleo once (Installation), then import what you need. Dates are @internationalized/date objects, re-exported from Camaleo.

import { RangeCalendar, CalendarDate, isWeekend } from "@camaleo/react";

Anatomy

The same parts as Calendar. The days between the start and the end get the range background.

title‹ ›start · range · endfooter
[data-slot="range-calendar"][data-slot="calendar-title"] · [data-slot="calendar-nav-button"] · [data-slot="calendar-footer"][data-slot="calendar-grid"] · [data-slot="calendar-cell"]

Examples

Every example is a real file you can copy. They all wear the skin, mode and brand from the bar above.

minimal · light · #3fa34d

Trip dates, October 2026

27
28
29
30
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

Stay dates, October 2026

27
28
29
30
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
Oct 18 – Oct 235 nights

Controlled, with a nights count

Click a check-in, then a check-out. end.compare(start) gives the nights, and the footer repeats the range in words.

Ski season, December 2026

29
30
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
Lifts open Dec 1 to Feb 28.

Min and max

minValue and maxValue keep the range inside the season, and the arrows stop at its first and last month.

Minimum stay, October 2026

27
28
29
30
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
Stays need at least 2 nights.

Minimum stay

Pick a single night. Check the length in onChange, set invalid and say the rule in the footer, which is a live status.

Cabin dates, October 2026

27
28
29
30
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
Oct 20 to 22 are booked. A stay can't cross them.

Booked nights

Try to cross Oct 20. By default a range can't span unavailable days, so nobody books a stay that overlaps another guest.

Time off, October 2026

27
28
29
30
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
Weekends don't count.10 work days

Across weekends

allowsNonContiguousRanges lets a range cross unavailable days. Count only the days that matter, like work days off.

Sprint 21, October 2026

28
29
30
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
Two weeks, Monday to Sunday.

Week starts on Monday

firstDayOfWeek="mon" makes a two-week sprint read as two full rows.

Your reservation, October 2026

27
28
29
30
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
Confirmed. Changes by phone only.

Read-only

readOnly shows a confirmed stay. People can still move through it with the keyboard and hear the dates.

In context

Ranges drive bookings, requests and reports. These are whole pieces of UI you can copy, in the skin you picked.

Seaside Inn · Monterey

Ocean-view room, breakfast included. $180 per night.

Stay dates, October 2026

27
28
29
30
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
3 nights

Total $540

Hotel stay with a priceCard · RangeCalendar · Button
18 days left

Request time off

Your manager, Priya Shah, gets the request.

Time off, November 2026

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
Weekends don't count.10 work days
Time off requestCard · Badge · RangeCalendar · Button

Sales report

Data until Oct 28, 2026.

Report period, October 2026

27
28
29
30
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
Report period with presetsCard · ButtonGroup · RangeCalendar · Button

Guidelines

Stay dates, October 2026

27
28
29
30
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
Oct 18 – 23 · 5 nights
DoSum up the range in words.

"5 nights" confirms the choice at a glance and is read by screen readers.

Stay dates, October 2026

27
28
29
30
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
Don’tOnly the highlight.

People count cells to know how long they picked, and get it wrong by one.

Cabin dates, October 2026

27
28
29
30
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
Oct 20 to 22 are booked. A stay can't cross them.
DoMark booked days before people pick.

isDateUnavailable stops the range at the booked nights, and the footer says why.

Cabin dates, October 2026

27
28
29
30
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
Oct 20 to 22 are booked. Pick other dates.
Don’tReject the range after it's picked.

Every day looked free, so the error comes as a surprise and people start over.

Props

PropTypeDefaultDescription
classNamestring—Class names for the root element.
styleCSSProperties—Inline styles for the root element.
disabledboolean—Disables every day and the arrows, and removes the grid from the tab order.
readOnlybooleanfalseShows the range without letting people change it. Days stay focusable.
invalidboolean—Marks the range as invalid, for rules your app checks, like a minimum stay.
localestring—BCP 47 locale for month names, weekdays and the first day of the week, e.g. "pt-BR". Defaults to the nearest I18nProvider or the browser.
weekdayStyleWeekdayStyle"short"How the weekday headers are written: "narrow", "short" (default) or "long".
previousLabelstring—Accessible name of the previous-month arrow. React Aria's localized "Previous" by default.
nextLabelstring—Accessible name of the next-month arrow. React Aria's localized "Next" by default.
footerReactNode—Content under the grid: the range in words, a nights count or a total.
value{ start: DateValue; end: DateValue } | null—The current value (controlled).
defaultValue{ start: DateValue; end: DateValue } | null—The default value (uncontrolled).
onChange(value: { start; end }) => void—Handler that is called when the value changes.
minValueDateValue | null—The minimum allowed date that a user may select.
maxValueDateValue | null—The maximum allowed date that a user may select.
autoFocusbooleanfalseWhether to automatically focus the calendar when it mounts.
focusedValueDateValue | null—Controls the currently focused date within the calendar.
defaultFocusedValueDateValue | null—The date that is focused when the calendar first mounts (uncontrolled).
onFocusChange(date: CalendarDate) => void—Handler that is called when the focused date changes.
firstDayOfWeek"sun" | "mon" | "tue" | "wed" | "thu" | "fri" | "sat"—The day that starts the week.
allowsNonContiguousRangesboolean—When combined with isDateUnavailable, determines whether non-contiguous ranges, i.e. ranges containing unavailable dates, may be selected.
isDateUnavailable(date: DateValue) => boolean—Callback that is called for each date of the calendar. If it returns true, then the date is unavailable. The second argument provides the current selection anchor date, if any. This can be used to adjust the available dates based on the user's first selected date.

DateRange is exported as the type of the value. React Aria's other range calendar props pass through, such as pageBehavior and createCalendar.

Styling hooks

Restyle it without forking: target the attributes, or set the tokens on a parent, a skin or the calendar itself.

Attributes

data-slot"range-calendar" · "calendar-header" · "calendar-grid" · "calendar-cell" · "calendar-footer"
data-selectedpresent on every day of the range
data-selection-start · data-selection-endpresent on the first · the last day of the range
data-todaypresent on today's date
data-unavailablepresent on days isDateUnavailable rejects
data-disabled · data-outside-monthpresent on days outside min/max · on days of another month

Tokens it reads

Color

--cml-accent--cml-accent-fg--cml-accent-text--cml-border--cml-border-control--cml-button-outline-bg--cml-button-outline-border--cml-button-outline-shadow--cml-button-solid-shadow--cml-card-border--cml-card-shadow--cml-fg--cml-fg-muted--cml-fg-secondary--cml-focus-offset--cml-focus-ring--cml-focus-width--cml-radius-surface--cml-surface-raised

Shape & size

--cml-border-width--cml-font-size-help--cml-radius-control-sm

Type & motion

--cml-duration-fast--cml-ease-standard--cml-font-control--cml-font-display--cml-font-label--cml-font-weight-display

This component only

--cml-button-solid-image--cml-calendar-bg--cml-calendar-cell-font--cml-calendar-cell-radius--cml-calendar-cell-size--cml-calendar-cell-size-font--cml-calendar-cell-weight--cml-calendar-header-fg--cml-calendar-header-size--cml-calendar-header-tracking--cml-calendar-header-transform--cml-calendar-header-weight--cml-calendar-nav-radius--cml-calendar-nav-size--cml-calendar-padding--cml-calendar-radius--cml-calendar-range-border--cml-calendar-range-fg--cml-calendar-selected-bg--cml-calendar-selected-border--cml-calendar-selected-fg--cml-calendar-title-font--cml-calendar-title-size--cml-calendar-title-tracking--cml-calendar-title-transform--cml-calendar-title-weight--cml-calendar-today-bg--cml-calendar-today-border--cml-calendar-today-fg--cml-calendar-width--cml-corner-shape--cml-disabled-opacity--cml-item-highlight
/* a softer band between the two ends */
.booking [data-slot="range-calendar"] {
  --cml-calendar-range-bg: color-mix(in oklab, var(--cml-accent) 8%, transparent);
}

Accessibility

Arrow keysMove focus one day, or one week up and down. After the first pick, the range follows focus.
Enter / SpacePicks the start, then the end of the range.
EscapeCancels a range that has a start but no end yet.
Home / EndMove focus to the first or last day of the month.
Page Up / Page DownMove to the previous or next month. With Shift, by a year.
Was this page helpful? · Edit this page on GitHub