Siteplane UI

Calendar

Choose a date, several dates or a date range.

Markdown

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
"use client";

import { Calendar } from "@/siteplane/components/calendar";

// The example's today, fixed like its dates, so every visitor sees the same
// calendar. An application leaves todayDate unset to use the device's date.
const today = new Date(2026, 9, 14);

export default function Example() {
  return (
    <Calendar
      aria-label="Appointment date"
      todayDate={today}
      defaultValue={new Date(2026, 9, 14)}
    />
  );
}

Installation

pnpm dlx shadcn@4.18.0 add @siteplane/calendar

Complete the one-time setup first. Registry JSON lists source files and dependencies.

For this page’s examples, also install:

pnpm dlx shadcn@4.18.0 add @siteplane/button

Usage

import { Calendar } from "@/siteplane/components/calendar";

<Calendar aria-label="Due date" defaultValue={new Date(2026, 9, 14)} />;

Calendar takes native Date values, like the date pickers; only the day counts.

Date range

Set selectionMode="range" to select a start and an end date. The value is a { from, to } object. The start, the end and the days between them fade in like a single selected day, and change at once when the user prefers reduced motion.

Trip dates, 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
"use client";

import { Calendar } from "@/siteplane/components/calendar";

export default function Example() {
  return (
    <Calendar
      selectionMode="range"
      aria-label="Trip dates"
      defaultValue={{ from: new Date(2026, 8, 7), to: new Date(2026, 8, 12) }}
    />
  );
}

Shortcuts

Pass shortcuts to a range calendar for quick choices such as "Last week". They show as links in the accent color between the heading and the days. A choice selects its range and shows the month the range starts in. One that starts or ends on a date the calendar doesn't allow is disabled.

Report 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
"use client";

import { Calendar } from "@/siteplane/components/calendar";

export default function Example() {
  return (
    <Calendar
      selectionMode="range"
      aria-label="Report period"
      defaultValue={{ from: new Date(2026, 8, 14), to: new Date(2026, 8, 21) }}
      shortcuts={[
        {
          label: "Last week",
          value: { from: new Date(2026, 8, 7), to: new Date(2026, 8, 13) },
        },
        {
          label: "Last month",
          value: { from: new Date(2026, 7, 1), to: new Date(2026, 7, 31) },
        },
        {
          label: "Last year",
          value: { from: new Date(2025, 0, 1), to: new Date(2025, 11, 31) },
        },
      ]}
    />
  );
}

Multiple dates

Set selectionMode="multiple" to select several separate dates. The value is an array of dates.

Meeting dates, 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
"use client";

import { Calendar } from "@/siteplane/components/calendar";

export default function Example() {
  return (
    <Calendar
      selectionMode="multiple"
      aria-label="Meeting dates"
      defaultValue={[new Date(2026, 8, 7), new Date(2026, 8, 10)]}
    />
  );
}

Unavailable dates

Use startMonth and endMonth for the booking window and disabled for dates that are already taken. Unavailable dates can't be selected.

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
"use client";

import { Calendar } from "@/siteplane/components/calendar";

const bookedDates = [
  new Date(2026, 9, 8),
  new Date(2026, 9, 9),
  new Date(2026, 9, 15),
  new Date(2026, 9, 22),
  new Date(2026, 9, 23),
];

// The example's today, fixed like its dates, so every visitor sees the same
// calendar. An application leaves todayDate unset to use the device's date.
const today = new Date(2026, 9, 14);

export default function Example() {
  return (
    <Calendar
      aria-label="Appointment date"
      todayDate={today}
      defaultMonth={new Date(2026, 9, 5)}
      startMonth={new Date(2026, 9, 1)}
      endMonth={new Date(2026, 10, 1)}
      disabled={[{ before: new Date(2026, 9, 5) }, ...bookedDates]}
    />
  );
}

Today button

Control value and month when your own actions change the date, such as a Today button that selects today and shows its month. The example fixes its today with todayDate, like its dates; without todayDate, today is the device's date.

Due 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
"use client";

import { Button } from "@/siteplane/components/button";
import { Calendar } from "@/siteplane/components/calendar";
import { useState } from "react";

// The example's today, fixed like its dates, so every visitor sees the same
// calendar. An application leaves todayDate unset to use the device's date.
const today = new Date(2026, 9, 14);

export default function Example() {
  const [value, setValue] = useState<Date | null>(new Date(2026, 9, 30));
  const [month, setMonth] = useState(new Date(2026, 9, 1));

  return (
    <div className="flex flex-col items-start gap-2">
      <Calendar
        aria-label="Due date"
        value={value}
        onValueChange={setValue}
        month={month}
        onMonthChange={setMonth}
        todayDate={today}
      />
      <Button
        size="sm"
        variant="outline"
        onClick={() => {
          setValue(today);
          setMonth(today);
        }}
      >
        Today
      </Button>
    </div>
  );
}

Locale

Pass locale to format one calendar differently from the surrounding locale provider. Month names, weekday order and the first day of the week follow the locale.

Appointment 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
"use client";

import { Calendar } from "@/siteplane/components/calendar";

// The example's today, fixed like its dates, so every visitor sees the same
// calendar. An application leaves todayDate unset to use the device's date.
const today = new Date(2026, 9, 14);

export default function Example() {
  return (
    <Calendar
      aria-label="Appointment date"
      todayDate={today}
      locale="de-DE"
      defaultValue={new Date(2026, 9, 14)}
    />
  );
}

Calendar options

CalendarOptions holds defaultMonth, disabled, startMonth, endMonth and todayDate, the options that Calendar shares with the calendarProps of Date Field, Date Picker, Date Range Picker and Date Time Picker. Define them once and pass the same object to a Calendar, or to a picker as calendarProps={booking}.

import {
  Calendar,
  type CalendarOptions,
} from "@/siteplane/components/calendar";

const booking: CalendarOptions = {
  startMonth: new Date(2026, 9, 1),
  endMonth: new Date(2027, 2, 1),
  disabled: [{ before: new Date(2026, 9, 12) }, { dayOfWeek: [0, 6] }],
};

<Calendar aria-label="Booking date" {...booking} />;

disabled takes a CalendarDayMatcher or a list of them. A matcher is a Date, a (date: Date) => boolean test, or a rule. In a rule, before and after match the days before or after a date, from and to a span of days with both ends included, and dayOfWeek the weekdays it lists, from 0 for Sunday to 6 for Saturday. A matched day shows struck out and can't be chosen. disabled={true} disables the whole calendar instead. startMonth and endMonth limit the months people can move to.

todayDate sets the day that counts as today, for an application whose today is not the device's, such as a demo fixed on one date or a schedule kept in another time zone. The calendar marks that day, and without a selected date it opens on its month. Without todayDate, today is the device's date.

API reference

Component properties
PropTypeDefaultDescription
selectionModesingle | multiple | rangesingleSelects the calendar behavior and value shape.
aria-labelstringunsetAccessible name of the calendar.
sizesm | md | lg | xlprovider / lgCalendar sizing.
localestringlocale contextLocal language/calendar formatting override.
value / defaultValueDate | null, Date[] or { from, to }uncontrolledSelection shape follows selectionMode.
onValueChange(value: Date | Date[] | { from, to }) => voidunsetReports the selection, in the shape of value.
month / defaultMonthDateselected date or todayThe month the calendar shows, as any day in it. With two months, a month outside the ones on screen shows first.
onMonthChange(month: Date) => voidunsetReports the first day of the month that takes focus.
disabledboolean | matcher | matcher[]falsetrue disables the calendar; matchers make the days they match unavailable.
readOnlybooleanfalseKeeps the current selection.
startMonth / endMonthDateunsetThe first and last month the calendar allows.
todayDateDatethe device's dateThe day marked as today; without a selected date, the calendar opens on its month.
aria-invalidAriaAttributes[aria-invalid]unsetMarks the selected days invalid.
numberOfMonthsnumber1Range mode only. Consecutive months shown side by side.
shortcuts{ label, value: { from, to } }[]unsetRange mode only. Quick choices shown under the heading.

Matchers and the options shared with the date pickers are described in Calendar options. The module exports CalendarProps with CalendarSingleProps, CalendarMultipleProps and CalendarRangeProps for the three selection modes, CalendarSelectionMode, CalendarOptions, CalendarDayMatcher, DateRange for a { from, to } value and CalendarRangeShortcut for a shortcut. The component renders its own header, month and year picker and month grid; it accepts no custom children. It shows one month, except in range mode with numberOfMonths={2}, which renders two consecutive months side by side with one shared selection and one Previous/Next pair. Each of the two months has its own heading with a month and year picker: choosing a month there moves both months so that the chosen one shows at that position, while Previous and Next page both months by one. With two months the shortcuts show once, above both. A shortcut, or a month you set, that leads to a month outside the two on screen shows that month first, also when it lies before them; a month already on screen stays where it is.

Read the installed source.

Usage notes

A selected day fills with the brand accent, and its number takes the text color for accent fills, --sp-rw-fg-on-accent, which a light brand sets dark (Theming).

Arrow keys move between days, and the month heading opens a month and year picker. That heading button and the picker use English accessible labels in every locale. todayDate moves the today mark and the first month, but screen readers still hear the device's date named as today, because React Aria names that day. Use DatePicker or DateField for compact date entry. The Calendar reports days as native Dates at local midnight; persist the day deliberately, for example as YYYY-MM-DD.