RangeCalendar

A range calendar displays one or more date grids and allows users to select a contiguous range of dates.

Theme 

December 2025

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
3
isDisabled 
Example
RangeCalendar.tsx
RangeCalendar.css
import {RangeCalendar} from './RangeCalendar';

<RangeCalendar />

Value

Use the value or defaultValue prop to set the selected date range, using objects in the @internationalized/date package. This library supports parsing date strings in multiple formats, manipulation across international calendar systems, time zones, etc.

February 2025

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
1

Selected range: February 3 – 12, 2025

import {parseDate, getLocalTimeZone} from '@internationalized/date';
import {useDateFormatter} from 'react-aria';
import {RangeCalendar} from './RangeCalendar';
import {useState} from 'react';

function Example() {
  let [range, setRange] = useState({
    start: parseDate('2025-02-03'),
    end: parseDate('2025-02-12')
  });
  let formatter = useDateFormatter({ dateStyle: 'long' });

  return (
    <>
      <RangeCalendar
        value={range}
        onChange={setRange}
      />
      <p>Selected range: {formatter.formatRange(
        range.start.toDate(getLocalTimeZone()),
        range.end.toDate(getLocalTimeZone())
      )}</p>
    </>
  );
}

International calendars

By default, RangeCalendar displays the value using the calendar system for the user's locale. Use <I18nProvider> to override the calendar system by setting the Unicode calendar locale extension. The onChange event always receives a date in the same calendar as the value or defaultValue (Gregorian if no value is provided), regardless of the displayed locale.

शक 1946 माघ

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
1
2
3
Locale
Calendar
import {I18nProvider} from 'react-aria-components';
import {parseDate} from '@internationalized/date';
import {RangeCalendar} from './RangeCalendar';

<I18nProvider locale="hi-IN-u-ca-indian">
  <RangeCalendar
    defaultValue={{
      start: parseDate('2025-02-03'),
      end: parseDate('2025-02-12')
    }} />
</I18nProvider>

Custom calendar systems

RangeCalendar also supports custom calendar systems that implement custom business rules, for example a fiscal year calendar that follows a 4-5-4 format, where month ranges don't follow the usual Gregorian calendar. See the @internationalized/date docs for an example implementation.

December 2025

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
import type {AnyCalendarDate} from '@internationalized/date';
import {CalendarDate, startOfWeek, toCalendar, GregorianCalendar} from '@internationalized/date';
import {RangeCalendar} from './RangeCalendar';

export default function Example() {
  return (
    <RangeCalendar
      firstDayOfWeek="sun"
      createCalendar={() => new Custom454()} />
  );
}

// See @internationalized/date docs linked above.

Validation

Use the minValue and maxValue props to set the valid date range. The isDateUnavailable callback prevents certain dates from being selected. Use allowsNonContiguousRanges to allow selecting ranges containing unavailable dates. For custom validation rules, set the isInvalid prop and the errorMessage slot.

Trip dates, December 2025

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
3
Maximum stay duration is 1 week
allowsNonContiguousRanges 
import {today, getLocalTimeZone} from '@internationalized/date';
import {RangeCalendar} from './RangeCalendar';
import {useState} from 'react';

function Example(props) {
  let now = today(getLocalTimeZone());
  let [range, setRange] = useState({
    start: now.add({days: 6}),
    end: now.add({ days: 14 })
  });
  let disabledRanges = [
    [now, now.add({ days: 5 })],
    [now.add({ days: 15 }), now.add({ days: 17 })],
    [now.add({ days: 23 }), now.add({ days: 24 })]
  ];
  let isInvalid = range.end.compare(range.start) > 7;

  return (
    <RangeCalendar
      {...props}
      aria-label="Trip dates"
      value={range}
      onChange={setRange}
      minValue={today(getLocalTimeZone())}
      isDateUnavailable={date => (
        disabledRanges.some((interval) =>
          date.compare(interval[0]) >= 0 && date.compare(interval[1]) <= 0
        )
      )}
      isInvalid={isInvalid}
      errorMessage={isInvalid ? 'Maximum stay duration is 1 week' : undefined} />
  );
}

Display options

Set the visibleDuration prop and render multiple CalendarGrid elements to display more than one month at a time. The pageBehavior prop controls whether pagination advances by a single month or multiple. The firstDayOfWeek prop overrides the locale-specified first day of the week.

Trip dates, December 2025 to January 2026

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
3
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
 
pageBehavior 
firstDayOfWeek 
import {RangeCalendar, Heading} from 'react-aria-components';
import {CalendarGrid, CalendarCell} from './RangeCalendar';
import {Button} from './Button';
import {useDateFormatter} from 'react-aria';
import {ChevronLeft, ChevronRight} from 'lucide-react';

// TODO: move this into the starter example.
function Example(props) {
  let monthFormatter = useDateFormatter({
    month: 'long',
    year: 'numeric',
  });

  return (
    <RangeCalendar
      {...props}
      aria-label="Trip dates"
      visibleDuration={{months: 2}}
      style={{display: 'flex', gap: 12, overflow: 'auto'}}
    >
      {({state}) => (
        [...Array(props.visibleDuration.months).keys()].map(i => (
          <div key={i} style={{flex: 1}}>
            <header style={{minHeight: 32}}>
              {i === 0 &&
                <Button slot="previous" variant="quiet">
                  <ChevronLeft />
                </Button>
              }
              <Heading>{monthFormatter.format(state.visibleRange.start.add({months: i}).toDate(state.timeZone))}</Heading>
              {i === props.visibleDuration.months - 1 &&
                <Button slot="next" variant="quiet">
                  <ChevronRight />
                </Button>
              }
            </header>
            <CalendarGrid offset={{months: i}}>
              {date => <CalendarCell date={date} />}
            </CalendarGrid>
          </div>
        ))
      )}
    </RangeCalendar>
  );
}

Controlling the focused date

Use the focusedValue or defaultFocusedValue prop to control which date is focused. This controls which month is visible. The onFocusChange event is called when a date is focused by the user.

July 2021

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
import {RangeCalendar} from './RangeCalendar';
import {Button} from './Button';
import {CalendarDate, today, getLocalTimeZone} from '@internationalized/date';
import {useState} from 'react';

function Example() {
  let defaultDate = new CalendarDate(2021, 7, 1);
  let [focusedDate, setFocusedDate] = useState(defaultDate);

  return (
    <div>
      <Button
        style={{marginBottom: 20}}
        onPress={() => setFocusedDate(today(getLocalTimeZone()))}>
        Today
      </Button>
      <RangeCalendar
        focusedValue={focusedDate}
        onFocusChange={setFocusedDate}
      />
    </div>
  );
}

Month and year pickers

You can also control the focused date via CalendarStateContext. This example shows month and year dropdown components that work inside any <RangeCalendar>.

December 2025

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
3
Example
MonthDropdown.tsx
YearDropdown.tsx
import {RangeCalendar} from 'react-aria-components';
import {CalendarGrid, CalendarCell} from './RangeCalendar';
import {MonthDropdown} from './MonthDropdown';
import {YearDropdown} from './YearDropdown';
import {Button} from './Button';
import {ChevronLeft, ChevronRight} from 'lucide-react';

<RangeCalendar>
  <header style={{display: 'flex', gap: 4}}>
    <Button slot="previous" variant="quiet">
      <ChevronLeft />
    </Button>
    <MonthDropdown />
    <YearDropdown />
    <Button slot="next" variant="quiet">
      <ChevronRight />
    </Button>
  </header>
  <CalendarGrid>
    {(date) => <CalendarCell date={date} />}
  </CalendarGrid>
</RangeCalendar>

API

September 2021SMTWTF5789101314151619202122232412326272930628S111825412176CellGridNext buttonPrevious button
<RangeCalendar>
  <Button slot="previous" />
  <Heading />
  <Button slot="next" />
  <CalendarGrid>
    <CalendarGridHeader>
      {day => <CalendarHeaderCell />}
    </CalendarGridHeader>
    <CalendarGridBody>
      {date => <CalendarCell date={date} />}
    </CalendarGridBody>
  </CalendarGrid>
  <Text slot="errorMessage" />
</RangeCalendar>

RangeCalendar

A range calendar displays one or more date grids and allows users to select a contiguous range of dates.

NameTypeDefault
visibleDurationDefault: {months: 1}
The amount of days that will be displayed at once. This affects how pagination works.
createCalendar(identifier: ) => Default:
A function to create a new Calendar object for a given calendar identifier. If not provided, the createCalendar function from @internationalized/date will be used.
allowsNonContiguousRangesbooleanDefault:
When combined with isDateUnavailable, determines whether non-contiguous ranges, i.e. ranges containing unavailable dates, may be selected.
isDateUnavailable(date: ) => booleanDefault:
Callback that is called for each date of the calendar. If it returns true, then the date is unavailable.
isDisabledbooleanDefault: false
Whether the calendar is disabled.
isReadOnlybooleanDefault: false
Whether the calendar value is immutable.
focusedValuenullDefault:
Controls the currently focused date within the calendar.
defaultFocusedValuenullDefault:
The date that is focused when the calendar first mounts (uncontrolled).
pageBehaviorDefault: visible
Controls the behavior of paging. Pagination either works by advancing the visible page by visibleDuration (default) or one unit of visibleDuration.
firstDayOfWeek'sun''mon''tue''wed''thu''fri''sat'Default:
The day that starts the week.
selectionAlignment'start''center''end'Default: 'center'
Determines the alignment of the visible months on initial render based on the current selection or current date if there is no selection.
children<>Default:
The children of the component. A function may be provided to alter the children based on component state.
value<>nullDefault:
The current value (controlled).
defaultValue<>nullDefault:
The default value (uncontrolled).
onChange(value: <<>>) => voidDefault:
Handler that is called when the value changes.

Default className: react-aria-RangeCalendar

Render PropCSS Selector
stateCSS Selector:
State of the range calendar.
isDisabledCSS Selector: [data-disabled]
Whether the calendar is disabled.
isInvalidCSS Selector: [data-invalid]
Whether the calendar is invalid.

CalendarGrid

A calendar grid displays a single grid of days within a calendar or range calendar which can be keyboard navigated and selected by the user.

NameTypeDefault
childrenReactElementReactElement[](date: ) => ReactElementDefault:
Either a function to render calendar cells for each date in the month, or children containing a <CalendarGridHeader>`` and<CalendarGridBody>` when additional customization is needed.
weekdayStyle'narrow''short''long'Default: "narrow"
The style of weekday names to display in the calendar grid header, e.g. single letter, abbreviation, or full day name.

Default className: react-aria-CalendarGrid

CalendarGridHeader

A calendar grid header displays a row of week day names at the top of a month.

NameType
children(day: string) => ReactElement
A function to render a <CalendarHeaderCell> for a weekday name.

Default className: react-aria-CalendarGridHeader

CalendarHeaderCell

A calendar header cell displays a week day name at the top of a column within a calendar.

NameType
childrenReactNode
The children of the component.

Default className: react-aria-CalendarHeaderCell

CalendarGridBody

A calendar grid body displays a grid of calendar cells within a month.

NameType
children(date: ) => ReactElement
A function to render a <CalendarCell> for a given date.

Default className: react-aria-CalendarGridBody

CalendarCell

A calendar cell displays a date cell within a calendar grid which can be selected by the user.

NameType
date
The date to render in the cell.
children<>
The children of the component. A function may be provided to alter the children based on component state.

Default className: react-aria-CalendarCell

Render PropCSS Selector
dateCSS Selector:
The date that the cell represents.
formattedDateCSS Selector:
The day number formatted according to the current locale.
isHoveredCSS Selector: [data-hovered]
Whether the cell is currently hovered with a mouse.
isPressedCSS Selector: [data-pressed]
Whether the cell is currently being pressed.
isSelectedCSS Selector: [data-selected]
Whether the cell is selected.
isSelectionStartCSS Selector: [data-selection-start]
Whether the cell is the first date in a range selection.
isSelectionEndCSS Selector: [data-selection-end]
Whether the cell is the last date in a range selection.
isFocusedCSS Selector: [data-focused]
Whether the cell is focused.
isFocusVisibleCSS Selector: [data-focus-visible]
Whether the cell is keyboard focused.
isDisabledCSS Selector: [data-disabled]
Whether the cell is disabled, according to the calendar's minValue, maxValue, and isDisabled props. Disabled dates are not focusable, and cannot be selected by the user. They are typically displayed with a dimmed appearance.
isOutsideVisibleRangeCSS Selector: [data-outside-visible-range]
Whether the cell is outside the visible range of the calendar. For example, dates before the first day of a month in the same week.
isOutsideMonthCSS Selector: [data-outside-month]
Whether the cell is outside the current month.
isUnavailableCSS Selector: [data-unavailable]
Whether the cell is unavailable, according to the calendar's isDateUnavailable prop. Unavailable dates remain focusable, but cannot be selected by the user. They should be displayed with a visual affordance to indicate they are unavailable, such as a different color or a strikethrough. Note that because they are focusable, unavailable dates must meet a 4.5:1 color contrast ratio, as defined by WCAG.
isInvalidCSS Selector: [data-invalid]
Whether the cell is part of an invalid selection.
isTodayCSS Selector: [data-today]
Whether the cell is today.