Skip to content

Date & time

Calendar

A date grid is deceptively hard: keyword navigation, locale week starts, unavailable days that stay focusable, and year jumps that do not require 120 clicks. React Aria owns the grid semantics; LUC owns every pixel of it.

Examples

Single date

Values are `CalendarDate` — a calendar date with no clock and no time zone, so 2026-09-18 is that day everywhere.

Review date, September 2026

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

Sep 18, 2026

Week starts, weekday names and month names follow it.

The heading, weekday row, segment order and numerals all come from the locale — nothing about the month grid is hard-coded to Sunday-start or to Latin digits.

Date range

One month on narrow screens, two on desktop. Both ends are inclusive, and the band is a continuous span rather than a row of pills.

Review period, September 2026

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

Sep 8 – Sep 18, 2026

Unavailable dates

A blocked day stays focusable and stays readable — it is struck through and underlined by a diagonal line, not just dimmed. Selection skips it rather than trapping the user.

Period with a blackout, September to October 2026

September – October 2026
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
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

21–24 September is a blackout window in this demo.

Month and year selection

Both are ARIA grids with roving focus: arrow keys move, Home/End jump within a row, Enter selects. Years page by decade, so 1998 is four clicks away from 2026.

MonthPicker

2026
January
February
March
April
May
June
July
August
September
October
November
December

YearPicker

2020–2029
2020
2021
2022
2023
2024
2025
2026
2027
2028
2029

Selected month and year

month: May 2026
year: 2024

The same two components power the calendar header's month/year jump — the component you use is the component the header uses.

Inside a locale provider

`LocaleProvider` sets the locale for every date and number control underneath, which is also how a server-rendered app avoids a hydration mismatch: pass the locale explicitly instead of relying on `navigator.language`.

Kalender, September 2026

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

Formatted: 18. Sept. 2026

Usage

import { Calendar } from "@luc/ui";
<Calendar aria-label="Review date" value={date} onValueChange={setDate} />

The value is a `CalendarDate` and `onValueChange` reports one or `null`. A calendar with no popover of its own is what you want inside a page section; wrap `DatePicker` around the same grid when the field should be a form control.

API

Read from the published TypeScript API, so a prop that is not listed here does not exist and a default that is listed is the one the compiler sees.

Exported symbols
ExportKindSignature
Calendar60 documented props<T extends DateValue = CalendarDate>({ locale, viewSwitch, onValueChange, footer, className, ...props }: CalendarProps<T>) => import("@types/react").JSX.Element
CalendarActionButtonexport({ className, type, ...props }: React.ComponentPropsWithRef<"button">) => import("@types/react").JSX.Element
CalendarFooter1 documented props({ className, ...props }: CalendarFooterProps) => import("@types/react").JSX.Element
RangeCalendar60 documented props<T extends DateValue = CalendarDate>({ locale, viewSwitch, months, onValueChange, footer, className, ...props }: RangeCalendarProps<T>) => import("@types/react").JSX.Element
Exported symbols
ExportKindSignature
LocaleProvider2 documented props({ locale, children }: LocaleProviderProps) => import("@types/react").JSX.Element
Exported symbols
ExportKindSignature
Chevron1 documented props({ direction }: { direction: "left" | "right" | "down"; }) => import("@types/react").JSX.Element
MonthPicker13 documented props({ value, onValueChange, year, defaultYear, onYearChange, minValue, maxValue, isMonthUnavailable, locale, withYearNavigation, footer, className, ...props }: MonthPickerProps) => import("@types/react"…
YearPicker11 documented props({ value, onValueChange, pageStart, defaultPageStart, onPageStartChange, yearsPerPage, minValue, maxValue, locale, className, ...props }: YearPickerProps) => import("@types/react").JSX.Element

Calendar props

Type: CalendarProps<T>

Values the compiler reads from the declaration.

Calendar props
PropTypeDefaultNotes
aria-describedby?inheritedstring | undefined—Identifies the element (or elements) that describes the object.
aria-details?inheritedstring | undefined—Identifies the element (or elements) that provide a detailed, extended description for the object.
aria-label?inheritedstring | undefined—Defines a string value that labels the current element.
aria-labelledby?inheritedstring | undefined—Identifies the element (or elements) that labels the current element.
autoFocus?inheritedboolean | undefinedWhether to automatically focus the calendar when it mounts.
className?string | undefined—
createCalendar?inherited((identifier: CalendarIdentifier) => ICalendar) | undefined—A function to create a new [Calendar](https://react-spectrum.adobe.com/internationalized/date/Calendar.html) object for a given calendar identifier. If not provided, the `createCa…
defaultFocusedValue?inheritedDateValue | null | undefined—The date that is focused when the calendar first mounts (uncontrolled).
defaultValue?inheritedT | null | undefined—The default value (uncontrolled).
dir?inheritedstring | undefined—
firstDayOfWeek?inherited"sun" | "mon" | "tue" | "wed" | "thu" | "fri" | "sat" | undefined—The day that starts the week.
focusedValue?inheritedDateValue | null | undefined—Controls the currently focused date within the calendar.
footer?ReactNode—Rendered under the grid — quick actions belong here, not in the grid.
hidden?inheritedboolean | undefined—
id?inheritedstring | undefined—The element's unique identifier. See [MDN](https://developer.mozilla.org/en-US/docs/Web/HTML/Global_attributes/id).
inert?inheritedboolean | undefined—
isDateUnavailable?inherited((date: DateValue) => boolean) | undefined—Callback that is called for each date of the calendar. If it returns true, then the date is unavailable.
isDisabled?inheritedboolean | undefinedWhether the calendar is disabled.
isInvalid?inheritedboolean | undefined—Whether the current selection is invalid according to application logic.
isReadOnly?inheritedboolean | undefinedWhether the calendar value is immutable.
lang?inheritedstring | undefined—
locale?string | undefined—BCP-47 locale. Pass it to keep server and client output identical.
maxValue?inheritedDateValue | null | undefined—The maximum allowed date that a user may select.
minValue?inheritedDateValue | null | undefined—The minimum allowed date that a user may select.
onAnimationEnd?inheritedAnimationEventHandler<HTMLDivElement> | undefined—
onAnimationEndCapture?inheritedAnimationEventHandler<HTMLDivElement> | undefined—
onAnimationIteration?inheritedAnimationEventHandler<HTMLDivElement> | undefined—
onAnimationIterationCapture?inheritedAnimationEventHandler<HTMLDivElement> | undefined—
onAnimationStart?inheritedAnimationEventHandler<HTMLDivElement> | undefined—
onAnimationStartCapture?inheritedAnimationEventHandler<HTMLDivElement> | undefined—
onAuxClick?inheritedMouseEventHandler<HTMLDivElement> | undefined—
onAuxClickCapture?inheritedMouseEventHandler<HTMLDivElement> | undefined—
onChange?inherited((value: MappedDateValue<T>) => void) | undefined—Handler that is called when the value changes.
onClick?inheritedMouseEventHandler<HTMLDivElement> | undefined—
onClickCapture?inheritedMouseEventHandler<HTMLDivElement> | undefined—
onContextMenu?inheritedMouseEventHandler<HTMLDivElement> | undefined—
onContextMenuCapture?inheritedMouseEventHandler<HTMLDivElement> | undefined—
onDoubleClick?inheritedMouseEventHandler<HTMLDivElement> | undefined—
onDoubleClickCapture?inheritedMouseEventHandler<HTMLDivElement> | undefined—
onFocusChange?inherited((date: CalendarDate) => void) | undefined—Handler that is called when the focused date changes.
onGotPointerCapture?inheritedPointerEventHandler<HTMLDivElement> | undefined—
onGotPointerCaptureCapture?inheritedPointerEventHandler<HTMLDivElement> | undefined—
onLostPointerCapture?inheritedPointerEventHandler<HTMLDivElement> | undefined—
onLostPointerCaptureCapture?inheritedPointerEventHandler<HTMLDivElement> | undefined—
onMouseDown?inheritedMouseEventHandler<HTMLDivElement> | undefined—
onMouseDownCapture?inheritedMouseEventHandler<HTMLDivElement> | undefined—
onMouseEnter?inheritedMouseEventHandler<HTMLDivElement> | undefined—
onMouseLeave?inheritedMouseEventHandler<HTMLDivElement> | undefined—
onMouseMove?inheritedMouseEventHandler<HTMLDivElement> | undefined—
onMouseMoveCapture?inheritedMouseEventHandler<HTMLDivElement> | undefined—
onMouseOut?inheritedMouseEventHandler<HTMLDivElement> | undefined—
onMouseOutCapture?inheritedMouseEventHandler<HTMLDivElement> | undefined—
onMouseOver?inheritedMouseEventHandler<HTMLDivElement> | undefined—
onMouseOverCapture?inheritedMouseEventHandler<HTMLDivElement> | undefined—
onMouseUp?inheritedMouseEventHandler<HTMLDivElement> | undefined—
onMouseUpCapture?inheritedMouseEventHandler<HTMLDivElement> | undefined—
onPointerCancel?inheritedPointerEventHandler<HTMLDivElement> | undefined—
onPointerCancelCapture?inheritedPointerEventHandler<HTMLDivElement> | undefined—
onPointerDown?inheritedPointerEventHandler<HTMLDivElement> | undefined—
onPointerDownCapture?inheritedPointerEventHandler<HTMLDivElement> | undefined—

LocaleProvider props

Type: LocaleProviderProps

Values the compiler reads from the declaration.

LocaleProvider props
PropTypeDefaultNotes
childrenReactNode—
locale?string | undefined—BCP-47 locale, e.g. `en-GB`, `de-DE`, `ja-JP`, `ar-EG`. Date and number controls follow the locale for segment order, weekday names, the first day of the week, the 12/24-hour cloc…

MonthPicker props

Type: MonthPickerProps

Values the compiler reads from the declaration.

MonthPicker props
PropTypeDefaultNotes
aria-label?string | undefined—
className?string | undefined—
defaultYear?number | undefined—
footer?ReactNode—Rendered under the grid — a "today" or "clear" affordance.
isMonthUnavailable?((firstDayOfMonth: CalendarDate) => boolean) | undefined—A month is unavailable when its first day is.
locale?string | undefined—
maxValue?DateValue | null | undefined—
minValue?DateValue | null | undefined—
onValueChange?((value: CalendarDate) => void) | undefined—
onYearChange?((year: number) => void) | undefined—
value?CalendarDate | null | undefined—Selected month. The day is ignored.
withYearNavigation?boolean | undefined—Year navigation row. Hidden inside a calendar that already has one.
year?number | undefined—Year on display. Controlled or `defaultYear`.
CalendarFooter props1 props

CalendarFooter props

Type: CalendarFooterProps

Values the compiler reads from the declaration.

CalendarFooter props
PropTypeDefaultNotes
childrenReactNode—
RangeCalendar props60 props

RangeCalendar props

Type: RangeCalendarProps<T>

Values the compiler reads from the declaration.

RangeCalendar props
PropTypeDefaultNotes
allowsNonContiguousRanges?inheritedboolean | undefined—When combined with `isDateUnavailable`, determines whether non-contiguous ranges, i.e. ranges containing unavailable dates, may be selected.
aria-describedby?inheritedstring | undefined—Identifies the element (or elements) that describes the object.
aria-details?inheritedstring | undefined—Identifies the element (or elements) that provide a detailed, extended description for the object.
aria-label?inheritedstring | undefined—Defines a string value that labels the current element.
aria-labelledby?inheritedstring | undefined—Identifies the element (or elements) that labels the current element.
autoFocus?inheritedboolean | undefinedWhether to automatically focus the calendar when it mounts.
className?string | undefined—
commitBehavior?inherited"select" | "reset" | "clear" | undefinedControls the behavior when a pointer is released outside the calendar or a blur occurs mid selection: - `clear`: clear the currently selected range of dates. - `reset`: reset the …
createCalendar?inherited((identifier: CalendarIdentifier) => ICalendar) | undefined—A function to create a new [Calendar](https://react-spectrum.adobe.com/internationalized/date/Calendar.html) object for a given calendar identifier. If not provided, the `createCa…
defaultFocusedValue?inheritedDateValue | null | undefined—The date that is focused when the calendar first mounts (uncontrolled).
defaultValue?inheritedRangeValue<T> | null | undefined—The default value (uncontrolled).
dir?inheritedstring | undefined—
firstDayOfWeek?inherited"sun" | "mon" | "tue" | "wed" | "thu" | "fri" | "sat" | undefined—The day that starts the week.
focusedValue?inheritedDateValue | null | undefined—Controls the currently focused date within the calendar.
footer?ReactNode—
hidden?inheritedboolean | undefined—
id?inheritedstring | undefined—The element's unique identifier. See [MDN](https://developer.mozilla.org/en-US/docs/Web/HTML/Global_attributes/id).
inert?inheritedboolean | undefined—
isDateUnavailable?inherited((date: DateValue, anchorDate: CalendarDate | null) => boolean) | undefined—Callback that is called for each date of the calendar. If it returns true, then the date is unavailable. The second argument provides the current selection anchor date, if any. Th…
isDisabled?inheritedboolean | undefinedWhether the calendar is disabled.
isInvalid?inheritedboolean | undefined—Whether the current selection is invalid according to application logic.
isReadOnly?inheritedboolean | undefinedWhether the calendar value is immutable.
lang?inheritedstring | undefined—
locale?string | undefined—
maxValue?inheritedDateValue | null | undefined—The maximum allowed date that a user may select.
minValue?inheritedDateValue | null | undefined—The minimum allowed date that a user may select.
months?1 | 2 | undefined—How many months are visible. One on narrow screens, two on desktop — the Showcase switches this with a media query rather than forcing two everywhere.
onAnimationEnd?inheritedAnimationEventHandler<HTMLDivElement> | undefined—
onAnimationEndCapture?inheritedAnimationEventHandler<HTMLDivElement> | undefined—
onAnimationIteration?inheritedAnimationEventHandler<HTMLDivElement> | undefined—
onAnimationIterationCapture?inheritedAnimationEventHandler<HTMLDivElement> | undefined—
onAnimationStart?inheritedAnimationEventHandler<HTMLDivElement> | undefined—
onAnimationStartCapture?inheritedAnimationEventHandler<HTMLDivElement> | undefined—
onAuxClick?inheritedMouseEventHandler<HTMLDivElement> | undefined—
onAuxClickCapture?inheritedMouseEventHandler<HTMLDivElement> | undefined—
onChange?inherited((value: RangeValue<MappedDateValue<T>>) => void) | undefined—Handler that is called when the value changes.
onClick?inheritedMouseEventHandler<HTMLDivElement> | undefined—
onClickCapture?inheritedMouseEventHandler<HTMLDivElement> | undefined—
onContextMenu?inheritedMouseEventHandler<HTMLDivElement> | undefined—
onContextMenuCapture?inheritedMouseEventHandler<HTMLDivElement> | undefined—
onDoubleClick?inheritedMouseEventHandler<HTMLDivElement> | undefined—
onDoubleClickCapture?inheritedMouseEventHandler<HTMLDivElement> | undefined—
onFocusChange?inherited((date: CalendarDate) => void) | undefined—Handler that is called when the focused date changes.
onGotPointerCapture?inheritedPointerEventHandler<HTMLDivElement> | undefined—
onGotPointerCaptureCapture?inheritedPointerEventHandler<HTMLDivElement> | undefined—
onLostPointerCapture?inheritedPointerEventHandler<HTMLDivElement> | undefined—
onLostPointerCaptureCapture?inheritedPointerEventHandler<HTMLDivElement> | undefined—
onMouseDown?inheritedMouseEventHandler<HTMLDivElement> | undefined—
onMouseDownCapture?inheritedMouseEventHandler<HTMLDivElement> | undefined—
onMouseEnter?inheritedMouseEventHandler<HTMLDivElement> | undefined—
onMouseLeave?inheritedMouseEventHandler<HTMLDivElement> | undefined—
onMouseMove?inheritedMouseEventHandler<HTMLDivElement> | undefined—
onMouseMoveCapture?inheritedMouseEventHandler<HTMLDivElement> | undefined—
onMouseOut?inheritedMouseEventHandler<HTMLDivElement> | undefined—
onMouseOutCapture?inheritedMouseEventHandler<HTMLDivElement> | undefined—
onMouseOver?inheritedMouseEventHandler<HTMLDivElement> | undefined—
onMouseOverCapture?inheritedMouseEventHandler<HTMLDivElement> | undefined—
onMouseUp?inheritedMouseEventHandler<HTMLDivElement> | undefined—
onMouseUpCapture?inheritedMouseEventHandler<HTMLDivElement> | undefined—
onPointerCancel?inheritedPointerEventHandler<HTMLDivElement> | undefined—
Chevron props1 props

Chevron props

Type: { direction: "left" | "right" | "down"; }

Values the compiler reads from the declaration.

Chevron props
PropTypeDefaultNotes
direction"right" | "left" | "down"—
YearPicker props11 props

YearPicker props

Type: YearPickerProps

Values the compiler reads from the declaration.

YearPicker props
PropTypeDefaultNotes
aria-label?string | undefined—
className?string | undefined—
defaultPageStart?number | undefined—
locale?string | undefined—
maxValue?DateValue | null | undefined—
minValue?DateValue | null | undefined—
onPageStartChange?((year: number) => void) | undefined—
onValueChange?((value: CalendarDate) => void) | undefined—
pageStart?number | undefined—First year of the visible page.
value?CalendarDate | null | undefined—
yearsPerPage?number | undefined—Years per page. Default 10, aligned to the decade — "2020–2029".

Guidelines

Do

  • Reach for `Calendar` when the grid is the content of the page — a review screen, a scheduler — and for `DatePicker` when the grid belongs behind a field in a form.
  • Pass `locale` (or wrap the subtree in `LocaleProvider`) instead of letting the browser decide: that is what keeps week start, month names and numerals identical on the server and in the browser.
  • Block a day with `isDateUnavailable` rather than removing it, so the day stays focusable, announced and visible as a struck-through cell.

Don't

  • Turn a date-only value into a JavaScript `Date`. `new Date("2026-09-18")` is UTC midnight, so west of Greenwich it reads back as the 17th; `dateOnlyFromInstant(instant, timeZone)` is the one way across, and it demands the zone.
  • Force `months={2}` into a narrow column: two months are 472 px wide, which is wider than a phone and clips the second grid.
  • Use a date grid to collect a time as well — that is `DateTimePicker`, and a calendar that quietly picked 00:00 would be inventing an answer.

Accessibility

React Aria owns the grid contract: one tab stop for the whole grid, arrow keys to move by day, Page Up and Page Down by month, Home and End to the ends of a week, and each cell announced with its full date and whether it is unavailable. A `Calendar` needs its own accessible name (`aria-label`), and a `RangeCalendar` announces the two ends as start and end rather than as two unrelated grids.

Luc UI v1.0Calendardark theme