Range Calendar

PreviousNext

A calendar component that allows users to select a date range.

Select a date range, November 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
29
30
1
2
3
4
5
6
import { RangeCalendar } from "@/components/ui/range-calendar"

export function RangeCalendarDemo() {
  return <RangeCalendar aria-label="Select a date range" />
}

Installation

pnpm dlx shadcn@latest add range-calendar

Usage

import { RangeCalendar } from "@/components/ui/range-calendar"
<RangeCalendar aria-label="Select a date range" />

About

The RangeCalendar component is built on top of React Aria Components and allows users to select a continuous or non-contiguous range of dates. It uses the @internationalized/date library for date manipulation.

Examples

Default

Select a date range, November 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
29
30
1
2
3
4
5
6
import { RangeCalendar } from "@/components/ui/range-calendar"

export function RangeCalendarDemo() {
  return <RangeCalendar aria-label="Select a date range" />
}
<RangeCalendar aria-label="Select a date range" />

Controlled

Select a date range, December 2024

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

Selected range: 2024-12-15 to 2024-12-22

"use client"

import { useState } from "react"
import { parseDate } from "@internationalized/date"

import { RangeCalendar } from "@/components/ui/range-calendar"

export function RangeCalendarControlled() {
  const [value, setValue] = useState({
    start: parseDate("2024-12-15"),
    end: parseDate("2024-12-22"),
  })

  return (
    <div className="space-y-4">
      <RangeCalendar
        aria-label="Select a date range"
        value={value}
        onChange={setValue}
      />
      <p className="text-muted-foreground text-sm">
        Selected range: {value.start.toString()} to {value.end.toString()}
      </p>
    </div>
  )
}
"use client"
 
import { useState } from "react"
import { parseDate } from "@internationalized/date"
 
import { RangeCalendar } from "@/components/ui/range-calendar"
 
export default function RangeCalendarControlled() {
  const [value, setValue] = useState({
    start: parseDate("2024-12-15"),
    end: parseDate("2024-12-22"),
  })
 
  return (
    <div className="space-y-4">
      <RangeCalendar
        aria-label="Select a date range"
        value={value}
        onChange={setValue}
      />
      <p className="text-muted-foreground text-sm">
        Selected range: {value.start.toString()} to {value.end.toString()}
      </p>
    </div>
  )
}

Disabled

Select a date range, November 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
29
30
1
2
3
4
5
6
import { RangeCalendar } from "@/components/ui/range-calendar"

export function RangeCalendarDisabled() {
  return <RangeCalendar aria-label="Select a date range" isDisabled />
}
<RangeCalendar aria-label="Select a date range" isDisabled />

Min and Max Values

Select a date range, November 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
29
30
1
2
3
4
5
6
"use client"

import { getLocalTimeZone, today } from "@internationalized/date"

import { RangeCalendar } from "@/components/ui/range-calendar"

export function RangeCalendarMinMax() {
  const now = today(getLocalTimeZone())

  return (
    <RangeCalendar
      aria-label="Select a date range"
      minValue={now}
      maxValue={now.add({ weeks: 4 })}
    />
  )
}
"use client"
 
import { getLocalTimeZone, today } from "@internationalized/date"
 
import { RangeCalendar } from "@/components/ui/range-calendar"
 
export default function RangeCalendarMinMax() {
  const now = today(getLocalTimeZone())
 
  return (
    <RangeCalendar
      aria-label="Select a date range"
      minValue={now}
      maxValue={now.add({ weeks: 4 })}
    />
  )
}

Unavailable Dates

Select a date range, November 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
29
30
1
2
3
4
5
6
"use client"

import { getLocalTimeZone, isWeekend, today } from "@internationalized/date"
import { useLocale } from "react-aria-components"

import { RangeCalendar } from "@/components/ui/range-calendar"

export function RangeCalendarUnavailableDates() {
  const now = today(getLocalTimeZone())
  const { locale } = useLocale()

  return (
    <RangeCalendar
      aria-label="Select a date range"
      minValue={now}
      isDateUnavailable={(date) => isWeekend(date, locale)}
    />
  )
}
"use client"
 
import { getLocalTimeZone, isWeekend, today } from "@internationalized/date"
import { useLocale } from "react-aria-components"
 
import { RangeCalendar } from "@/components/ui/range-calendar"
 
export default function RangeCalendarUnavailableDates() {
  const now = today(getLocalTimeZone())
  const { locale } = useLocale()
 
  return (
    <RangeCalendar
      aria-label="Select a date range"
      minValue={now}
      isDateUnavailable={(date) => isWeekend(date, locale)}
    />
  )
}

Non-Contiguous Ranges

Select a date range, November 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
29
30
1
2
3
4
5
6
"use client"

import { RangeCalendar } from "@/components/ui/range-calendar"

export function RangeCalendarNonContiguous() {
  return (
    <RangeCalendar aria-label="Select a date range" allowsNonContiguousRanges />
  )
}
<RangeCalendar
  aria-label="Select a date range"
  allowsNonContiguousRanges
/>

API Reference

RangeCalendar

PropTypeDefaultDescription
value{ start: DateValue, end: DateValue }-The current value (controlled)
defaultValue{ start: DateValue, end: DateValue }-The default value (uncontrolled)
onChange(value: { start: DateValue, end: DateValue }) => void-Handler called when the value changes
minValueDateValue-The minimum allowed date
maxValueDateValue-The maximum allowed date
isDateUnavailable(date: DateValue) => boolean-Function to determine if a date is unavailable
allowsNonContiguousRangesbooleanfalseWhether non-contiguous ranges are allowed
isDisabledbooleanfalseWhether the calendar is disabled
isReadOnlybooleanfalseWhether the calendar is read-only
autoFocusbooleanfalseWhether the calendar should auto focus
focusedValueDateValue-Controls which date is focused
errorMessagestring-Error message to display
classNamestring-Additional CSS classes

See the React Aria RangeCalendar documentation for more props and detailed information.

Visual Design

The range calendar uses a continuous visual style for selected date ranges:

  • Start Date: Rounded on the left side with primary background
  • End Date: Rounded on the right side with primary background
  • Middle Dates: Flat edges with primary background, connected visually using pseudo-elements
  • Gap Filling: Uses ::before and ::after pseudo-elements to fill gaps between date cells

This creates a seamless, continuous selection appearance that makes it clear which dates are included in the range.

Internationalization

The RangeCalendar component automatically formats dates based on the user's locale. The @internationalized/date library handles calendar systems and localization.

import { parseDate } from "@internationalized/date"
 
// ISO 8601 format
const range = {
  start: parseDate("2024-12-15"),
  end: parseDate("2024-12-22"),
}

Calendar Systems

The range calendar supports multiple calendar systems through the @internationalized/date library:

  • Gregorian
  • Buddhist
  • Hebrew
  • Indian
  • Islamic (Hijri)
  • Japanese
  • Persian
  • Taiwan

See the @internationalized/date documentation for more information.

Keyboard Navigation

The range calendar supports full keyboard navigation:

  • Arrow Keys: Navigate between dates
  • Enter/Space: Select the start or end date of the range
  • Page Up/Down: Navigate to the previous/next month
  • Home/End: Navigate to the first/last day of the week
  • Shift + Page Up/Down: Navigate to the previous/next year

Non-Contiguous Ranges

By default, the range calendar only allows contiguous date ranges. When allowsNonContiguousRanges is enabled, users can select multiple separate date ranges:

<RangeCalendar
  aria-label="Select date ranges"
  allowsNonContiguousRanges
/>

This is useful for scenarios like:

  • Selecting multiple non-consecutive vacation periods
  • Choosing available dates across different weeks
  • Booking multiple separate time slots

Use Cases

The range calendar is ideal for:

  • Date Range Selection: When users need to pick a continuous date range
  • Booking Systems: Hotel reservations, car rentals, etc.
  • Scheduling: Event planning and duration selection
  • Filtering: Date range filters for reports and data
  • Availability: Showing and selecting available date periods

Accessibility

The range calendar is built with accessibility in mind:

  • Proper ARIA labels and roles
  • Full keyboard navigation support
  • Focus management
  • Screen reader announcements
  • High contrast mode support

Always provide an aria-label or aria-labelledby prop to describe the calendar's purpose.