# Calendar

Choose a date, several dates or a date range.

Appointment date calendar

[Complete TSX file](/examples/calendar/default.tsx)

```tsx
"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

Complete [one-time setup](/docs/installation/) first.

```bash
pnpm dlx shadcn@4.18.0 add @siteplane/calendar
```

With npm: `npx shadcn@4.18.0 add @siteplane/calendar`. [Registry JSON](/registry/calendar.json) lists source files and dependencies.

For this page’s examples, also install:

```bash
pnpm dlx shadcn@4.18.0 add @siteplane/button
```

## Usage

```tsx
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.

Date range

[Complete TSX file](/examples/calendar/range.tsx)

```tsx
"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.

Date range with shortcuts

[Complete TSX file](/examples/calendar/shortcuts.tsx)

```tsx
"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.

Multiple dates

[Complete TSX file](/examples/calendar/multiple.tsx)

```tsx
"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.

Booked dates unavailable

[Complete TSX file](/examples/calendar/constraints.tsx)

```tsx
"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 with a Today button

[Complete TSX file](/examples/calendar/controlled.tsx)

```tsx
"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.

German calendar

[Complete TSX file](/examples/calendar/locale.tsx)

```tsx
"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](/docs/components/date-field/), [Date Picker](/docs/components/date-picker/), [Date Range Picker](/docs/components/date-range-picker/) and [Date Time Picker](/docs/components/date-time-picker/). Define them once and pass the same object to a Calendar, or to a picker as `calendarProps={booking}`.

```tsx
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

| Prop | Type | Default | Description |
| --- | --- | --- | --- |
| selectionMode | single \| multiple \| range | single | Selects the calendar behavior and value shape. |
| aria-label | string | unset | Accessible name of the calendar. |
| size | sm \| md \| lg \| xl | provider / lg | Calendar sizing. |
| locale | string | locale context | Local language/calendar formatting override. |
| value / defaultValue | Date \| null, Date[] or { from, to } | uncontrolled | Selection shape follows selectionMode. |
| onValueChange | (value: Date \| Date[] \| { from, to }) => void | unset | Reports the selection, in the shape of value. |
| month / defaultMonth | Date | selected date or today | The month the calendar shows, as any day in it. With two months, a month outside the ones on screen shows first. |
| onMonthChange | (month: Date) => void | unset | Reports the first day of the month that takes focus. |
| disabled | boolean \| matcher \| matcher[] | false | true disables the calendar; matchers make the days they match unavailable. |
| readOnly | boolean | false | Keeps the current selection. |
| startMonth / endMonth | Date | unset | The first and last month the calendar allows. |
| todayDate | Date | the device's date | The day marked as today; without a selected date, the calendar opens on its month. |
| aria-invalid | AriaAttributes[aria-invalid] | unset | Marks the selected days invalid. |
| numberOfMonths | number | 1 | Range mode only. Consecutive months shown side by side. |
| shortcuts | { label, value: { from, to } }[] | unset | Range mode only. Quick choices shown under the heading. |
Matchers and the options shared with the date pickers are described in
[Calendar options](#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](/source/calendar).

## 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](/docs/customization/theming/#override-component-colors)).

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`.
