Skip to content

ComponentsDate & Time

Calendar

An inline month grid for picking a single date, several dates or a date range. Supports min/max bounds, unavailable dates, multiple visible months, any locale and calendar system, and full keyboard navigation.

React AriaSource

Appointment date, 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

Installation

pnpm dlx shadcn@latest add @desyne/calendar

The CLI installs dependencies and any other components this one uses.

Usage

tsx
import { Calendar, RangeCalendar } from "@/components/ui/calendar";
import { getLocalTimeZone, today } from "@internationalized/date";
tsx
<Calendar aria-label="Due date" defaultValue={today(getLocalTimeZone())} />

Dates are @internationalized/date objects

Values are CalendarDate (or CalendarDateTime / ZonedDateTime) objects from @internationalized/date, not JavaScript Dates. Create them with today(getLocalTimeZone()) or parseDate("2027-03-15"), compare them with a.compare(b), and convert with date.toDate(timeZone) when you need a Date. The calendar follows the user's locale, calendar system and first day of the week automatically.

When to use

  • Calendar — an always-visible grid, for scheduling screens, booking flows and sidebars where the month context matters.
  • Date Picker — a compact field that opens this calendar in a popover. The right default inside forms.
  • Date Field — typed entry only, for dates users already know (birthdays, document dates) where a grid adds nothing.
  • RangeCalendar — a start and end date. Use Date Range Picker inside forms.

Anatomy

tsx
<Calendar>                 {/* or <RangeCalendar> */}
  <header>
    <Button slot="previous" />
    <Heading />            {/* "March 2027"; visually hidden with dropdowns */}
    {/* captionLayout="dropdown": month <Select> + year <Select> */}
    <Button slot="next" />
  </header>
  <CalendarGrid>           {/* one per visible month */}
    <CalendarGridHeader>   {/* weekday names */}
    <CalendarGridBody>
      <CalendarCell />     {/* one per day */}
    </CalendarGridBody>
  </CalendarGrid>
  <Text slot="errorMessage" />   {/* when errorMessage is set */}
</Calendar>
PartRendersNotes
Calendar<div>Single date, or several dates with selectionMode="multiple". data-slot="calendar".
RangeCalendar<div>A contiguous start/end range. data-slot="range-calendar".
Previous / next<button>Ghost icon Buttons. Disabled automatically at minValue / maxValue.
Heading<h2 aria-hidden>The visible month (or range of months), formatted for the locale. Screen readers get the month from the grid label instead.
Caption selectsSelect › <button> + listbox popoverWith captionLayout set to a dropdown layout. Labelled with the localized words for month and year. data-slot="calendar-caption" on the wrapper.
Grid<table role="grid">Rendered once per month in visibleDuration. Weekdays use the short style.
Cell<td> › <div role="button">Each date. All states are exposed as data attributes.
Error message<span slot="errorMessage">Shown below the grid whenever errorMessage is set.

The header, grid and cells are built in, so the only thing you pass is props. For a custom layout, compose React Aria's Calendar parts directly and reuse the cell styles from the source.

Examples

Controlled

Use value and onChange to own the selected date. Format it for display with DateFormatter and the current locale from useLocale().

Delivery date, 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

Delivery: Thursday, October 8, 2026

Range

RangeCalendar selects a start and end date. Click (or press Enter) once to set the start, and again to set the end. The value is { start, end }.

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

Min and max dates

minValue and maxValue disable dates outside the bounds. Disabled dates can't be focused, and the previous and next buttons disable themselves at the edges.

Pickup date, 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

Pickup is available for the next three weeks.

Month and year dropdowns

captionLayout="dropdown" replaces the heading with a month select and a year select, so users can jump years at a time. That's the right choice for dates far from today, such as a birth date. The arrows still page one month at a time.

The year list runs from fromYear to toYear. Without them it uses the years of minValue and maxValue, then falls back to 100 years ago through 10 years ahead. Months outside minValue / maxValue are disabled in the month list.

Date of birth, June 1990

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

Caption layouts

captionLayout takes four values: label (the default heading), dropdown (both selects), dropdown-months (a month select and the year as text) and dropdown-years (the month as text and a year select). Month names and years follow the locale and calendar system.

Delivery date, 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

RangeCalendar takes the same props. With several visible months, the selects follow the focused month, and picking a month scrolls it into view. Here the window is limited to 11 months, so dropdown-months is enough.

Stay dates, October to November 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
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

Bookings open 11 months ahead. Jump straight to a month from the header.

Unavailable dates

isDateUnavailable marks individual dates (holidays, booked days, weekends) as unavailable. Unlike disabled dates, they stay focusable so keyboard and screen reader users can still reach them, and they're struck through.

Meeting date, 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

Ranges across unavailable dates

By default a range can't include an unavailable date. Set allowsNonContiguousRanges to let the range span them, for example a leave request across a weekend. For RangeCalendar, isDateUnavailable also receives the anchor date (the first date clicked), so you can make availability depend on the start date.

Leave request, 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

Multiple months

visibleDuration={{ months: 2 }} shows consecutive months side by side (stacked on narrow screens). pageBehavior="single" pages one month at a time instead of a whole visible page.

Campaign dates, October to November 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
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

Multiple dates

selectionMode="multiple" lets users toggle any number of separate dates. The value is a read-only array.

On-call days, 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

2 days selected

First day of the week

The first weekday comes from the locale (Sunday in en-US, Monday in most of Europe). Override it with firstDayOfWeek.

Sprint start, 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

Locales and calendar systems

Wrap the calendar in I18nProvider to set a locale. Month and weekday names, digits, text direction and the calendar system all follow it. Add a -u-ca- extension for non-Gregorian calendars (ja-JP-u-ca-japanese, th-TH-u-ca-buddhist). The value you receive is still a CalendarDate in that calendar; convert with toCalendar(date, new GregorianCalendar()) before storing it.

Locale

Date, Oktober 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

Controlling the visible month

focusedValue and onFocusChange control which date has focus, and therefore which month is visible. Use them for "Today" or "Jump to" buttons. defaultFocusedValue sets only the initial month.

Release date, March 2027

28
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

Validation

Set isInvalid from your own logic and pass errorMessage to explain it. The selected date turns red, and the message is shown below the grid and linked to it for screen readers.

Deployment date, 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
Deploys are frozen on weekends. Pick a weekday.

Disabled and read-only

isDisabled makes the whole calendar non-interactive. isReadOnly keeps it focusable and navigable, but the value can't change.

Disabled calendar, 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
isDisabled

Read-only calendar, 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
isReadOnly

Recipes

Booking with a minimum stay

A RangeCalendar with already-booked nights marked unavailable, a minimum-stay rule shown through isInvalid and errorMessage, and a live price summary.

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

Lakeside cabin

$185 / night · 2-night minimum

Check-in
—
Check-out
—
Nights
—
$0

Appointment scheduler

Pick a weekday within the next six weeks, then a time slot from a ToggleButtonGroup. Changing the date resets the slot.

Appointment date, 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

Pick a day

Available times appear once you choose a date. Weekends are closed.

Range presets

Preset buttons set both value and focusedValue, so the calendar jumps to the chosen period. maxValue blocks future dates in a reporting context.

Reporting 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

Accessibility

  • The calendar is a role="application" group containing one role="grid" table per visible month, labelled by the visible month range.
  • Give the calendar an accessible name with aria-label or aria-labelledby. It's combined with the visible month when announced.
  • When the visible month changes, the new month is announced through a live region. Selecting a date or completing a range announces the selection.
  • Each cell's label includes the full date (weekday, day, month and year), plus "Today" and "selected" where relevant.
  • Unavailable dates stay focusable and are announced as unavailable. Disabled dates (outside minValue / maxValue) are skipped.
  • Today is marked with a dot, not just color, and unavailable dates use a strikethrough.
  • With errorMessage, the message is linked to the calendar with aria-describedby.

Keyboard

KeyAction
TabMoves between the previous button, next button and the grid
← / →Previous / next day (reversed in RTL locales)
↑ / ↓Same day in the previous / next week
Home / EndFirst / last day of the month
Page Up / Page DownPrevious / next month
Shift+Page Up / Shift+Page DownPrevious / next year
Enter / SpaceSelects the focused date (in a range: sets the start, then the end)
EscCancels a range selection in progress

With a dropdown captionLayout, the month and year selects sit between the previous and next buttons in the tab order. Enter, Space or ↓ opens a select, typing jumps to a matching month or year, and the grid moves to the chosen month.

Styling

Data attributes

On the Calendar / RangeCalendar root:

AttributePresent when
data-disabledisDisabled is true
data-invalidisInvalid is true

On each date cell:

AttributePresent when
data-selectedThe date is selected (or inside the selected range)
data-selection-start / data-selection-endFirst / last date of a range
data-todayThe date is today
data-focused / data-focus-visibleThe cell has focus / keyboard focus
data-hovered / data-pressedHovered with a mouse / being pressed
data-disabledOutside minValue / maxValue, or the calendar is disabled
data-unavailableisDateUnavailable returned true
data-outside-monthThe date belongs to the previous or next month
data-outside-visible-rangeThe date is outside the visible range
data-invalidThe date is part of an invalid selection

Slots

data-slotElement
calendarCalendar root
range-calendarRangeCalendar root
calendar-captionWrapper around the month / year selects

Customizing

  • className styles the root (default w-fit p-3). It accepts a function of { isDisabled, isInvalid, state }.
  • Frame the calendar with classes like rounded-lg border bg-card, as in the examples, or leave it bare inside a card or popover.
  • Selected dates use the brand color; range middles use accent. Change the theme tokens to restyle every calendar at once.
  • Cells are size-8. For a different density, copy the component and adjust cellBase.

API Reference

Calendar

Prop

Type

Also accepts every prop of React Aria's Calendar, except children.

RangeCalendar

Accepts every Calendar prop above except selectionMode, with these differences:

Prop

Type

Also accepts every prop of React Aria's RangeCalendar, except children.