- Alert
- Avatar
- Badge
- Breadcrumb
- Button
- Button Group
- Calendar
- Card
- Carousel
- Chart
- Checkbox
- Checkbox Group
- ComboBox
- Command
- Data Table
- Date Field
- Date Picker
- Date Range Picker
- Dialog
- Disclosure
- Disclosure Group
- Drawer
- Empty
- Field
- File Trigger
- Form
- Grid List
- Hover Card
- Input
- Input Group
- Input OTP
- Item
- Kbd
- Label
- ListBox
- Menu
- Number Field
- Pagination
- Popover
- Progress Bar
- Radio Group
- Range Calendar
- Resizable
- Search Field
- Select
- Separator
- Sheet
- Sidebar
- Skeleton
- Slider
- Sonner
- Spinner
- Switch
- Table
- Tabs
- Tag Group
- Text Field
- Textarea
- Time Field
- Toast
- Toggle Button
- Toggle Button Group
- Tooltip
- Tree
- Typography
Select a date range, November 2025
November 2025
| S | M | T | W | T | F | S |
|---|---|---|---|---|---|---|
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
pnpmnpmyarnbunpnpm 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
November 2025
| S | M | T | W | T | F | S |
|---|---|---|---|---|---|---|
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
December 2024
| S | M | T | W | T | F | S |
|---|---|---|---|---|---|---|
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
November 2025
| S | M | T | W | T | F | S |
|---|---|---|---|---|---|---|
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
November 2025
| S | M | T | W | T | F | S |
|---|---|---|---|---|---|---|
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
November 2025
| S | M | T | W | T | F | S |
|---|---|---|---|---|---|---|
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
November 2025
| S | M | T | W | T | F | S |
|---|---|---|---|---|---|---|
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
| Prop | Type | Default | Description |
|---|---|---|---|
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 |
minValue | DateValue | - | The minimum allowed date |
maxValue | DateValue | - | The maximum allowed date |
isDateUnavailable | (date: DateValue) => boolean | - | Function to determine if a date is unavailable |
allowsNonContiguousRanges | boolean | false | Whether non-contiguous ranges are allowed |
isDisabled | boolean | false | Whether the calendar is disabled |
isReadOnly | boolean | false | Whether the calendar is read-only |
autoFocus | boolean | false | Whether the calendar should auto focus |
focusedValue | DateValue | - | Controls which date is focused |
errorMessage | string | - | Error message to display |
className | string | - | 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
::beforeand::afterpseudo-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.
Related Components
- Calendar - For selecting a single date
- Date Range Picker - Combines range calendar with date fields
- Date Picker - For selecting a single date with a popover
- Date Field - For keyboard-only date entry