Calendar

Month grid for picking a single date, built on React Aria, with keyboard navigation and month paging.

import { CalendarDate } from "@internationalized/date"
import { Calendar } from "@pockets-ui/components"

Dates are @internationalized/date values, not JavaScript Dates — a CalendarDate has no time and no time zone, so it can't shift a day when it crosses one. Note its month is 1-based: new CalendarDate(2026, 7, 15) is 15 July 2026.

Anatomy

PartRole
Calendar.RootOwns the value, bounds, and availability rules.
Calendar.ActionRowThe header strip above the grid.
Calendar.ActionButtonMonth paging. slot="previous" / slot="next".
Calendar.HeadingThe current month and year.
Calendar.GridThe grid itself.
Calendar.HeaderWeekday header row — a render function per day.
Calendar.HeaderCellOne weekday label.
Calendar.BodyDay cells — a render function per date.
Calendar.CellOne day.

Basic

The full composition, spelled out. The examples below reuse it as a DemoCalendar wrapper so each one shows only the prop that's varying.

Event 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
Code
import { Calendar } from "@pockets-ui/components"
import { ChevronLeftIcon, ChevronRightIcon } from "./shared"

export default function CalendarBasic() {
  return (
    <Calendar.Root aria-label="Event date">
      <Calendar.ActionRow>
        <Calendar.ActionButton slot="previous">
          <ChevronLeftIcon />
        </Calendar.ActionButton>
        <Calendar.Heading />
        <Calendar.ActionButton slot="next">
          <ChevronRightIcon />
        </Calendar.ActionButton>
      </Calendar.ActionRow>
      <Calendar.Grid>
        <Calendar.Header>
          {(day) => <Calendar.HeaderCell>{day}</Calendar.HeaderCell>}
        </Calendar.Header>
        <Calendar.Body>{(date) => <Calendar.Cell date={date} />}</Calendar.Body>
      </Calendar.Grid>
    </Calendar.Root>
  )
}

Default value

Appointment date, July 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
Code
import { CalendarDate } from "@internationalized/date"
import { DemoCalendar } from "./shared"

export default function CalendarDefaultValue() {
  return (
    <DemoCalendar
      aria-label="Appointment date"
      defaultValue={new CalendarDate(2026, 7, 15)}
    />
  )
}

Min and max dates

Dates outside minValue/maxValue can't be selected or focused. defaultFocusedValue decides which month opens without selecting anything.

Booking date, July 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
Code
import { CalendarDate } from "@internationalized/date"
import { DemoCalendar } from "./shared"

export default function CalendarMinMax() {
  return (
    <DemoCalendar
      aria-label="Booking date"
      defaultFocusedValue={new CalendarDate(2026, 7, 15)}
      minValue={new CalendarDate(2026, 7, 6)}
      maxValue={new CalendarDate(2026, 7, 24)}
    />
  )
}

Unavailable dates

isDateUnavailable blocks scattered dates inside an otherwise valid range — booked days, holidays, blackout dates — while keeping them visible.

Delivery date, July 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
Code
import { CalendarDate } from "@internationalized/date"
import { DemoCalendar } from "./shared"

export default function CalendarUnavailableDates() {
  return (
    <DemoCalendar
      aria-label="Delivery date"
      defaultFocusedValue={new CalendarDate(2026, 7, 15)}
      isDateUnavailable={(date) => date.day === 13 || date.day === 21}
    />
  )
}

Disabled

Unavailable 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
Code
import { DemoCalendar } from "./shared"

export default function CalendarDisabled() {
  return <DemoCalendar aria-label="Unavailable calendar" isDisabled />
}

Related

  • DatePicker — this grid in a popover, with a date field.
  • DateField — the segmented text input on its own.