Skip to content

Calendar

A month grid for picking a date or a date range, always visible on the page.

Preview

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 add @syntara/react @syntara/tokens
import { Calendar, RangeCalendar } from '@syntara/react';

Usage

import { getLocalTimeZone, today } from '@internationalized/date';
import { Calendar } from '@syntara/react';

export function AppointmentDate() {
  return <Calendar aria-label="Appointment date" minValue={today(getLocalTimeZone())} onChange={(date) => console.log(date.toString())} />;
}

Examples

Range

RangeCalendar picks a start and end date.

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

Limits and unavailable dates

minValue/maxValue disable dates; isDateUnavailable strikes out dates that can't be booked.

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

Two months

visibleDuration shows months side by side; paging moves by the whole duration.

Reporting period, 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

Accessibility

KeysAction
←or→Previous / next day (mirrored in right-to-left locales).
↑or↓Same day in the previous / next week.
Page UporPage DownPrevious / next month.
ShiftPage UporPage DownPrevious / next year.
HomeorEndFirst / last day of the week.
EnterorSpaceSelects the focused date; in a range calendar the first press sets the start, the second the end.
EscapeRange calendar: cancels a half-picked range.
TabMoves between the previous/next buttons and the grid.
  • The grid is role="grid"; each date has a full label (e.g. "Monday, September 14, 2026") so the weekday header is hidden from assistive tech.
  • The visible month is announced when it changes.
  • Today is marked with a dot and bold weight; unavailable dates are struck through and stay readable (text.subtle); disabled dates are muted and not focusable.
  • Previous/next buttons have localised names and flip direction in right-to-left layouts.
  • Motion follows prefers-reduced-motion: colour fades remain; the press dip, selected-day spring and month slide only run when motion is allowed.

Guidelines

Do

  • Use an inline Calendar when the date is the main task on the screen (booking, scheduling).
  • Set minValue/maxValue rather than validating after the fact.
  • Use isDateUnavailable for dates that exist but can't be chosen, so people see why.

Don’t

  • Don't use an inline calendar in a dense form — use DatePicker.
  • Don't use a calendar for dates far from today, like a date of birth — type it in a DatePicker.

API reference

Calendar

aria-label
string

Accessible name; the visible month is appended. Required unless labelled by aria-labelledby.

value / defaultValue
DateValue | null

The selected date, controlled or initial (from @internationalized/date).

onChange
(value: DateValue) => void

Called when a date is selected.

minValue / maxValue
DateValue

Earliest and latest selectable dates; dates outside are disabled.

isDateUnavailable
(date: DateValue) => boolean

Marks dates that can be focused but not selected (e.g. fully booked).

focusedValue / defaultFocusedValue
DateValue

The date that has keyboard focus, which also sets the visible month.

visibleDuration
{ months: number }

How many months to show side by side.

Default { months: 1 }

firstDayOfWeek
  • sun
  • mon
  • tue
  • wed
  • thu
  • fri
  • sat

Overrides the locale's first day of the week.

isDisabled / isReadOnly
boolean

Disables the whole calendar, or keeps it focusable but unchangeable.

Default false

isInvalid
boolean

Shows the selection in the danger colour and errorMessage.

Default false

errorMessage
string

Shown under the grid when isInvalid is true.

RangeCalendar

value / defaultValue
{ start: DateValue; end: DateValue } | null

The selected range, controlled or initial.

onChange
(value: { start: DateValue; end: DateValue }) => void

Called when both ends of a range are picked.

allowsNonContiguousRanges
boolean

Allow ranges that span unavailable dates.

Default false

Tokens

The semantic tokens this component reads, grouped by what they control. Swatches show this site’s theme; change a tenant’s brand and the component follows with no code change.

Colour16
text.defaultborder.defaultsurface.defaulttext.subtleborder.strongsurface.selectedfocus.ringtext.disabledborder.subtleaction.primary.bgaction.primary.fgaction.primary.hoverfeedback.danger.solidfeedback.danger.onSolidfeedback.danger.bgfeedback.danger.fg
Type11
font.bodyfont.size.mdline-height.normalfont.headingfont.weight.semiboldfont.heading-trackingline-height.tightfont.size.smfont.weight.regularfont.weight.mediumline-height.snug
Space and size7
control-heightspace.1space.8space.3space.2space.6space.4
Shape2
radius.buttonradius.pill
Depth1
shadow.raised
Motion6
motion.duration.fastmotion.easingmotion.duration.normalmotion.easing-outmotion.duration.springmotion.spring