Calendar
A month grid for picking a date or a date range, always visible on the page.
Preview
October 2026
| S | M | T | W | T | F | S |
|---|---|---|---|---|---|---|
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/tokensimport { 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.
October 2026
| S | M | T | W | T | F | S |
|---|---|---|---|---|---|---|
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.
October 2026
| S | M | T | W | T | F | S |
|---|---|---|---|---|---|---|
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.
October – November 2026
| S | M | T | W | T | F | S |
|---|---|---|---|---|---|---|
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 |
| 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 | 1 | 2 | 3 | 4 | 5 |
Accessibility
| Keys | Action |
|---|---|
| ←or→ | Previous / next day (mirrored in right-to-left locales). |
| ↑or↓ | Same day in the previous / next week. |
| Page UporPage Down | Previous / next month. |
| ShiftPage UporPage Down | Previous / next year. |
| HomeorEnd | First / last day of the week. |
| EnterorSpace | Selects the focused date; in a range calendar the first press sets the start, the second the end. |
| Escape | Range calendar: cancels a half-picked range. |
| Tab | Moves 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-labelstringAccessible name; the visible month is appended. Required unless labelled by aria-labelledby.
value / defaultValueDateValue | nullThe selected date, controlled or initial (from @internationalized/date).
onChange(value: DateValue) => voidCalled when a date is selected.
minValue / maxValueDateValueEarliest and latest selectable dates; dates outside are disabled.
isDateUnavailable(date: DateValue) => booleanMarks dates that can be focused but not selected (e.g. fully booked).
focusedValue / defaultFocusedValueDateValueThe 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 }firstDayOfWeeksunmontuewedthufrisat
Overrides the locale's first day of the week.
isDisabled / isReadOnlybooleanDisables the whole calendar, or keeps it focusable but unchangeable.
Default
falseisInvalidbooleanShows the selection in the danger colour and errorMessage.
Default
falseerrorMessagestringShown under the grid when isInvalid is true.
RangeCalendar
value / defaultValue{ start: DateValue; end: DateValue } | nullThe selected range, controlled or initial.
onChange(value: { start: DateValue; end: DateValue }) => voidCalled when both ends of a range are picked.
allowsNonContiguousRangesbooleanAllow 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