---
id: "packages/active-ui/calendar"
title: "Calendar"
description: "A month grid to pick a day, several days or a range, in the language and with the today your application passes."
url: "/packages/active-ui/calendar"
locale: "en"
revision: "61a6c0ffa6a56507"
---

# Calendar

`Calendar` shows one or more months and lets the reader pick a day, several days or a range. It is
built on [react-day-picker](https://daypicker.dev) and takes all of its props. The package adds
the look, a year and month list built on [Select](/packages/active-ui/select), and two required
props that keep the calendar from guessing: the **locale** and **today**. For a date typed or
picked in a form field, use the composed [`DateField`](/packages/active-ui/date-field), which pairs a field with this calendar.

**Example: One day, in the language you pass**

Client: Runs entirely in your browser. Source: `examples/ui/calendar.tsx`.

```tsx
// apps/docs/examples/ui/calendar.tsx
'use client';

import {useState} from 'react';

import {getDateFnsLocale} from '@workspace/active-format';
import {Calendar} from '@workspace/active-ui/components/primitives/calendar';
import {
  NativeSelect,
  NativeSelectOption,
} from '@workspace/active-ui/components/primitives/native-select';

// A fixed "today", so every reader sees the same month. An application
// passes the day of its own presentation zone (see the page).
const TODAY = new Date(2026, 9, 20);

const LANGUAGES = [
  ['en', 'English'],
  ['nl', 'Dutch'],
  ['de', 'German'],
  ['ar-EG', 'Arabic (Egypt)'],
] as const;

function isoDay(date: Date | undefined): string {
  if (!date) return 'none';
  const pad = (n: number) => String(n).padStart(2, '0');
  return `${date.getFullYear()}-${pad(date.getMonth() + 1)}-${pad(date.getDate())}`;
}

/**
 * One day picked in a month grid. The calendar takes its words, labels and
 * week start from the locale you pass, and its "today" from you: change the
 * language to see both follow. The chosen day is printed as an ISO date.
 */
export default function CalendarExample() {
  const [language, setLanguage] = useState('en');
  const [day, setDay] = useState<Date | undefined>(new Date(2026, 9, 23));
  return (
    <div className="flex flex-col items-center gap-4">
      <NativeSelect
        aria-label="Language"
        size="sm"
        value={language}
        onChange={(event) => setLanguage(event.target.value)}
      >
        {LANGUAGES.map(([code, name]) => (
          <NativeSelectOption key={code} value={code}>
            {name}
          </NativeSelectOption>
        ))}
      </NativeSelect>
      <Calendar
        mode="single"
        selected={day}
        onSelect={setDay}
        defaultMonth={TODAY}
        today={TODAY}
        locale={getDateFnsLocale(language)}
        className="rounded-md border"
      />
      <p className="text-muted-foreground text-sm">
        Chosen: <code>{isoDay(day)}</code>
      </p>
    </div>
  );
}
```

## Usage

```tsx
import {getDateFnsLocale} from '@workspace/active-format';
import {Calendar} from '@workspace/active-ui/components/primitives/calendar';

<Calendar
  mode="single"
  selected={day}
  onSelect={setDay}
  locale={getDateFnsLocale(presentation)}
  today={today}
/>;
```

## What your application provides

- **`locale`** (required): a date-fns locale, from `getDateFnsLocale` of
  [active-format](/packages/active-format) for your resolved presentation. It decides the month
  and day names, the first day of the week and the digits. The accessible labels ("previous
  month", "today") come from react-day-picker for English, Dutch, German, French and Spanish, and
  from the browser's `Intl` data for any other language, so no locale is refused. Without a locale
  the calendar would render US English.
- **`today`** (required): the reader's current day as a local-midnight `Date`, in the time zone of
  your presentation, for example `dateFromIsoDate(todayIsoDate(timeZone))` from active-format.
  It marks today, opens the first month and bounds the default navigation. The calendar does not
  read the runtime clock, which would follow the device's zone instead of the one your application
  presents.
- The selection, its mode and its type: `mode="single"` gives a `Date`, `"multiple"` an array,
  `"range"` a `{from, to}` object.

## A range, disabled days and lists

**Example: A range over two months, with disabled days**

Client: Runs entirely in your browser. Source: `examples/ui/calendar-range.tsx`.

```tsx
// apps/docs/examples/ui/calendar-range.tsx
'use client';

import {useState} from 'react';

import {getDateFnsLocale} from '@workspace/active-format';
import {Calendar} from '@workspace/active-ui/components/primitives/calendar';

const TODAY = new Date(2026, 9, 20);

/** The shape of a range in `mode="range"`: its first and last day. */
interface DateRange {
  from: Date | undefined;
  to?: Date | undefined;
}
const LOCALE = getDateFnsLocale('en');

function isoDay(date: Date | undefined): string {
  if (!date) return '…';
  const pad = (n: number) => String(n).padStart(2, '0');
  return `${date.getFullYear()}-${pad(date.getMonth() + 1)}-${pad(date.getDate())}`;
}

/**
 * A range over two months, with the days before today and the weekends
 * disabled, and month and year chosen from lists. The modes, the disabled
 * matcher and the range type are react-day-picker's; the calendar passes
 * them through.
 */
export default function CalendarRangeExample() {
  const [range, setRange] = useState<DateRange | undefined>({
    from: new Date(2026, 9, 26),
    to: new Date(2026, 10, 4),
  });
  return (
    <div className="flex flex-col items-center gap-4">
      <Calendar
        mode="range"
        selected={range}
        onSelect={setRange}
        numberOfMonths={2}
        defaultMonth={TODAY}
        today={TODAY}
        locale={LOCALE}
        disabled={[{before: TODAY}, {dayOfWeek: [0, 6]}]}
        captionLayout="dropdown"
        startMonth={new Date(2026, 0, 1)}
        endMonth={new Date(2030, 11, 1)}
        className="rounded-md border"
      />
      <p className="text-muted-foreground text-sm">
        From <code>{isoDay(range?.from)}</code> to{' '}
        <code>{isoDay(range?.to)}</code>
      </p>
    </div>
  );
}
```

- `mode="range"` picks a first and last day; `numberOfMonths` shows several months beside each
  other on a wide screen and below each other on a narrow one.
- `disabled` takes react-day-picker's matchers: dates, `{before}`, `{after}`, `{dayOfWeek}`, or a
  function. A disabled day cannot be chosen and is skipped by the arrow keys.
- `captionLayout="dropdown"` replaces the month caption with a month and a year list, between
  `startMonth` and `endMonth`. The lists are this package's `Select`, so a list of many years
  scrolls in the package's scroll area instead of a browser popup.

## Accessibility and keyboard

- The days form a grid: the arrow keys move by a day or a week, `Home` and `End` to the start and
  end of the week, `Page Up` and `Page Down` by a month, with `Shift` by a year. `Enter` or
  `Space` chooses.
- Each day is a button named by its full date; today and the chosen days are announced as such.
- In a right-to-left language the navigation arrows turn around.

## API

| Prop | Type | Required | Description |
| --- | --- | --- | --- |
| `locale` | `DayPickerLocale` | yes | The date-fns locale of your presentation, for example `getDateFnsLocale(presentation)` from active-format. It sets the month and day names, the first day of the week and the digits; the accessible labels follow its language. Required: without it DayPicker renders US English. |
| `today` | `Date` | yes | The reader's current day, as a local-midnight `Date` in the time zone of your presentation, for example `dateFromIsoDate(todayIsoDate(timeZone))`. It marks today, opens the first month and bounds the default navigation. Required: the calendar does not read the device's clock. |
| `buttonVariant` | `"default" \| "destructive" \| "ghost" \| "link" \| "outline" \| "secondary" \| null` | no | The button variant of the previous and next month buttons. |

### Other parts

**CalendarDayButton**

| Prop | Type | Required | Description |
| --- | --- | --- | --- |
| `locale` | `Partial<DayPickerLocale>` | no | The locale for the day's label; inside `Calendar`, the calendar's own. |

> [!NOTE]
> **Based on shadcn/ui**
>
> This page adapts the Calendar documentation of shadcn/ui (MIT licence) to this
> package; see [Third-party notices](/third-party-notices).
