---
title: Calendar
subtitle: An easily stylable calendar component.
description: A high-quality, unstyled React calendar that is easy to customize.
---

# Calendar

A high-quality, unstyled React calendar that is easy to customize.

## Demo

### CSS Modules

This example shows how to implement the component using CSS Modules.

```tsx
/* index.tsx */
'use client';
import * as React from 'react';
import { format } from 'date-fns/format';
import { Calendar } from '@base-ui/react/calendar';
import { ChevronLeft, ChevronRight } from 'lucide-react';
import styles from './calendar.module.css';

export default function ExampleCalendar() {
  return (
    <Calendar.Root className={styles.Root}>
      {({ visibleDate }) => (
        <React.Fragment>
          <header className={styles.Header}>
            <Calendar.DecrementMonth className={styles.DecrementMonth}>
              <ChevronLeft />
            </Calendar.DecrementMonth>
            <span className={styles.HeaderLabel}>{format(visibleDate, 'MMMM yyyy')}</span>
            <Calendar.IncrementMonth className={styles.IncrementMonth}>
              <ChevronRight />
            </Calendar.IncrementMonth>
          </header>
          <Calendar.DayGrid className={styles.DayGrid}>
            <Calendar.DayGridHeader className={styles.DayGridHeader}>
              <Calendar.DayGridHeaderRow className={styles.DayGridHeaderRow}>
                {(day) => (
                  <Calendar.DayGridHeaderCell
                    value={day}
                    key={day.getTime()}
                    className={styles.DayGridHeaderCell}
                  />
                )}
              </Calendar.DayGridHeaderRow>
            </Calendar.DayGridHeader>
            <Calendar.DayGridBody className={styles.DayGridBody}>
              {(week) => (
                <Calendar.DayGridRow
                  value={week}
                  key={week.getTime()}
                  className={styles.DayGridRow}
                >
                  {(day) => (
                    <Calendar.DayGridCell
                      value={day}
                      key={day.getTime()}
                      className={styles.DayGridCell}
                    >
                      <Calendar.DayButton className={styles.DayButton} />
                    </Calendar.DayGridCell>
                  )}
                </Calendar.DayGridRow>
              )}
            </Calendar.DayGridBody>
          </Calendar.DayGrid>
        </React.Fragment>
      )}
    </Calendar.Root>
  );
}
```

```css
/* calendar.module.css */
.Root {
  border: 1px solid var(--calendar-root-border-color);
  border-radius: 8px;
  height: 312px;
  display: flex;
  flex-direction: column;

  --calendar-root-color: var(--color-gray-900);
  --calendar-root-border-color: var(--color-gray-500);
  --calendar-button-hover-bg-color: #e0f2fe;
  --calendar-button-focus-border-color: #0ea5e9;
  --calendar-day-grid-separator-bg-color: #9ca3af;
  --calendar-day-grid-header-color: var(--color-gray-500);

  --calendar-cell-selected-bg-color: #7dd3fc;
  --calendar-cell-outside-month-color: var(--color-gray-400);
  --calendar-cell-disabled-color: var(--color-gray-500);
  --calendar-cell-current-border-color: var(--color-gray-500);
  --calendar-cell-unavailable-color: #f87171;

  @media (prefers-color-scheme: dark) {
    --calendar-button-hover-bg-color: #075985;
    --calendar-cell-selected-bg-color: #0369a1;
  }
}

.Header {
  box-sizing: border-box;
  padding: 8px 12px;
  height: 40px;
  display: flex;
  justify-content: space-between;
  align-items: center;
}

.HeaderLabel {
  color: var(--calendar-root-color);
}

.DecrementMonth,
.IncrementMonth {
  border: none;
  user-select: none;
  background-color: transparent;
  border-radius: 4px;
  color: var(--calendar-root-color);
  margin: 0 6px;
  padding: 0;
  display: flex;

  &:hover {
    background-color: var(--calendar-button-hover-bg-color);
  }

  &:focus-visible {
    outline: 2px solid var(--calendar-button-focus-border-color);
  }

  &[data-disabled] {
    pointer-events: none;
    color: var(--calendar-cell-disabled-color);
  }
}

.DayGrid {
  padding: 12px;
  height: 276px;
  display: flex;
  flex-direction: column;
  gap: 4px;
  z-index: 1;
  position: relative;
}

.DayGridBody {
  display: flex;
  flex-direction: column;
  row-gap: 2px;
}

.DayGridRow,
.DayGridHeaderRow {
  display: flex;
  justify-content: center;
}

.DayGridHeaderCell {
  width: 36px;
  text-align: center;
  font-size: 0.75rem;
  color: var(--calendar-day-grid-header-color);
  font-weight: 500;
  padding: 0;
}

.DayGridCell {
  padding: 0;
}

.DayButton {
  background: none;
  padding: 0;
  font: inherit;
  height: 36px;
  width: 36px;
  position: relative;
  user-select: none;
  border: none;
  background-color: transparent;
  outline: none;
  box-sizing: border-box;
  border-radius: 4px;
  color: var(--calendar-root-color);

  &::before {
    content: '';
    position: absolute;
    inset: 2px;
    border-radius: 4px;
    border: none;
    z-index: -1;
    background-color: transparent;
  }

  &::after {
    content: '';
    border-radius: 4px;
    position: absolute;
    inset: 2px;
  }

  &:not([data-outside-month]):focus-visible {
    &::after {
      outline: 2px solid var(--calendar-button-focus-border-color);
    }
  }

  &:not([data-selected]):hover::before {
    background-color: var(--calendar-button-hover-bg-color);
  }

  &[data-selected]:not([data-outside-month])::before {
    background-color: var(--calendar-cell-selected-bg-color);
  }

  &[data-disabled] {
    pointer-events: none;
  }

  &:not([data-outside-month])[data-disabled] {
    color: var(--calendar-cell-disabled-color);
  }

  &:not([data-outside-month])[data-invalid] {
    text-decoration: line-through;
  }

  &[data-outside-month] {
    color: var(--calendar-cell-outside-month-color);
    pointer-events: none;
  }

  &[data-current]:not([data-selected], :focus-visible)::after {
    outline: 1px solid var(--calendar-cell-current-border-color);
  }

  &:not([data-disabled], [data-outside-month])[data-unavailable] {
    text-decoration: line-through;
    color: var(--calendar-cell-unavailable-color);
  }
}
```

## Anatomy

Import the component and assemble its parts:

```jsx title="Anatomy"
import { Calendar } from '@base-ui/react/calendar';

<Calendar.Root>
  <Calendar.DecrementMonth />
  <Calendar.IncrementMonth />
  <Calendar.DayGrid>
    <Calendar.DayGridHeader>
      <Calendar.DayGridHeaderRow>
        <Calendar.DayGridHeaderCell />
      </Calendar.DayGridHeaderRow>
    </Calendar.DayGridHeader>
    <Calendar.DayGridBody>
      <Calendar.DayGridRow>
        <Calendar.DayGridCell>
          <Calendar.DayButton />
        </Calendar.DayGridCell>
      </Calendar.DayGridRow>
    </Calendar.DayGridBody>
  </Calendar.DayGrid>
</Calendar.Root>;
```

## Examples

### Unavailable dates

You can use the `isDateUnavailable()` prop to mark specific dates as unavailable. Unavailable dates are visually indicated and cannot be selected, but are focusable.

This example disables weekends, US holidays, and the first Monday of every month.

## Demo

### CSS Modules

This example shows how to implement the component using CSS Modules.

```tsx
/* index.tsx */
'use client';
import * as React from 'react';
import { format } from 'date-fns/format';
import { Calendar } from '@base-ui/react/calendar';
import { ChevronLeft, ChevronRight } from 'lucide-react';
import styles from './calendar.module.css';

const holidays: Array<[number, number]> = [
  [0, 1], // New Year's Day
  [6, 4], // Independence Day
  [10, 11], // Veterans Day
  [11, 25], // Christmas Day
];

function isDateUnavailable(date: Date) {
  const day = date.getDay();
  const month = date.getMonth();
  const dayOfMonth = date.getDate();

  // Weekends
  if (day === 0 || day === 6) {
    return true;
  }

  // US holidays
  if (holidays.some(([m, d]) => month === m && dayOfMonth === d)) {
    return true;
  }

  // First Monday of every month (maintenance day)
  if (day === 1 && dayOfMonth <= 7) {
    return true;
  }

  return false;
}

export default function UnavailableDatesCalendar() {
  return (
    <Calendar.Root
      className={styles.Root}
      isDateUnavailable={isDateUnavailable}
      aria-label="Appointment date"
    >
      {({ visibleDate }) => (
        <React.Fragment>
          <header className={styles.Header}>
            <Calendar.DecrementMonth className={styles.DecrementMonth}>
              <ChevronLeft />
            </Calendar.DecrementMonth>
            <span className={styles.HeaderLabel}>{format(visibleDate, 'MMMM yyyy')}</span>
            <Calendar.IncrementMonth className={styles.IncrementMonth}>
              <ChevronRight />
            </Calendar.IncrementMonth>
          </header>
          <Calendar.DayGrid className={styles.DayGrid}>
            <Calendar.DayGridHeader className={styles.DayGridHeader}>
              <Calendar.DayGridHeaderRow className={styles.DayGridHeaderRow}>
                {(day) => (
                  <Calendar.DayGridHeaderCell
                    value={day}
                    key={day.getTime()}
                    className={styles.DayGridHeaderCell}
                  />
                )}
              </Calendar.DayGridHeaderRow>
            </Calendar.DayGridHeader>
            <Calendar.DayGridBody className={styles.DayGridBody}>
              {(week) => (
                <Calendar.DayGridRow
                  value={week}
                  key={week.getTime()}
                  className={styles.DayGridRow}
                >
                  {(day) => (
                    <Calendar.DayGridCell
                      value={day}
                      key={day.getTime()}
                      className={styles.DayGridCell}
                    >
                      <Calendar.DayButton className={styles.DayButton} />
                    </Calendar.DayGridCell>
                  )}
                </Calendar.DayGridRow>
              )}
            </Calendar.DayGridBody>
          </Calendar.DayGrid>
        </React.Fragment>
      )}
    </Calendar.Root>
  );
}
```

```css
/* calendar.module.css */
.Root {
  border: 1px solid var(--calendar-root-border-color);
  border-radius: 8px;
  height: 312px;
  display: flex;
  flex-direction: column;

  --calendar-root-color: var(--color-gray-900);
  --calendar-root-border-color: var(--color-gray-500);
  --calendar-button-hover-bg-color: #e0f2fe;
  --calendar-button-focus-border-color: #0ea5e9;
  --calendar-day-grid-separator-bg-color: #9ca3af;
  --calendar-day-grid-header-color: var(--color-gray-500);

  --calendar-cell-selected-bg-color: #7dd3fc;
  --calendar-cell-outside-month-color: var(--color-gray-400);
  --calendar-cell-disabled-color: var(--color-gray-500);
  --calendar-cell-current-border-color: var(--color-gray-500);
  --calendar-cell-unavailable-color: #f87171;

  @media (prefers-color-scheme: dark) {
    --calendar-button-hover-bg-color: #075985;
    --calendar-cell-selected-bg-color: #0369a1;
  }
}

.Header {
  box-sizing: border-box;
  padding: 8px 12px;
  height: 40px;
  display: flex;
  justify-content: space-between;
  align-items: center;
}

.HeaderLabel {
  color: var(--calendar-root-color);
}

.DecrementMonth,
.IncrementMonth {
  border: none;
  user-select: none;
  background-color: transparent;
  border-radius: 4px;
  color: var(--calendar-root-color);
  margin: 0 6px;
  padding: 0;
  display: flex;

  &:hover {
    background-color: var(--calendar-button-hover-bg-color);
  }

  &:focus-visible {
    outline: 2px solid var(--calendar-button-focus-border-color);
  }

  &[data-disabled] {
    pointer-events: none;
    color: var(--calendar-cell-disabled-color);
  }
}

.DayGrid {
  padding: 12px;
  height: 276px;
  display: flex;
  flex-direction: column;
  gap: 4px;
  z-index: 1;
  position: relative;
}

.DayGridBody {
  display: flex;
  flex-direction: column;
  row-gap: 2px;
}

.DayGridRow,
.DayGridHeaderRow {
  display: flex;
  justify-content: center;
}

.DayGridHeaderCell {
  width: 36px;
  text-align: center;
  font-size: 0.75rem;
  color: var(--calendar-day-grid-header-color);
  font-weight: 500;
  padding: 0;
}

.DayGridCell {
  padding: 0;
}

.DayButton {
  background: none;
  padding: 0;
  font: inherit;
  height: 36px;
  width: 36px;
  position: relative;
  user-select: none;
  border: none;
  background-color: transparent;
  outline: none;
  box-sizing: border-box;
  border-radius: 4px;
  color: var(--calendar-root-color);

  &::before {
    content: '';
    position: absolute;
    inset: 2px;
    border-radius: 4px;
    border: none;
    z-index: -1;
    background-color: transparent;
  }

  &::after {
    content: '';
    border-radius: 4px;
    position: absolute;
    inset: 2px;
  }

  &:not([data-outside-month]):focus-visible {
    &::after {
      outline: 2px solid var(--calendar-button-focus-border-color);
    }
  }

  &:not([data-selected]):hover::before {
    background-color: var(--calendar-button-hover-bg-color);
  }

  &[data-selected]:not([data-outside-month])::before {
    background-color: var(--calendar-cell-selected-bg-color);
  }

  &[data-disabled] {
    pointer-events: none;
  }

  &:not([data-outside-month])[data-disabled] {
    color: var(--calendar-cell-disabled-color);
  }

  &:not([data-outside-month])[data-invalid] {
    text-decoration: line-through;
  }

  &[data-outside-month] {
    color: var(--calendar-cell-outside-month-color);
    pointer-events: none;
  }

  &[data-current]:not([data-selected], :focus-visible)::after {
    outline: 1px solid var(--calendar-cell-current-border-color);
  }

  &:not([data-disabled], [data-outside-month])[data-unavailable] {
    text-decoration: line-through;
    color: var(--calendar-cell-unavailable-color);
  }
}
```

### Validation

You can use the `minDate` and `maxDate` props to limit the range of selectable dates.

## Demo

### CSS Modules

This example shows how to implement the component using CSS Modules.

```css
/* index.module.css */
.Wrapper {
  display: flex;
  flex-flow: row wrap;
  gap: 24px;
  justify-content: center;
}
```

```tsx
/* index.tsx */
'use client';
import * as React from 'react';
import { format } from 'date-fns/format';
import { Calendar } from '@base-ui/react/calendar';
import { ChevronLeft, ChevronRight } from 'lucide-react';
import indexStyles from './index.module.css';
import styles from './calendar.module.css';

export default function MinMaxDateCalendars() {
  const today = new Date();
  return (
    <div className={indexStyles.Wrapper}>
      <ValidationCalendar minDate={today} />
      <ValidationCalendar maxDate={today} />
    </div>
  );
}

function ValidationCalendar(props: Calendar.Root.Props) {
  return (
    <Calendar.Root className={styles.Root} {...props}>
      {({ visibleDate }) => (
        <React.Fragment>
          <header className={styles.Header}>
            <Calendar.DecrementMonth className={styles.DecrementMonth}>
              <ChevronLeft />
            </Calendar.DecrementMonth>
            <span className={styles.HeaderLabel}>{format(visibleDate, 'MMMM yyyy')}</span>
            <Calendar.IncrementMonth className={styles.IncrementMonth}>
              <ChevronRight />
            </Calendar.IncrementMonth>
          </header>
          <Calendar.DayGrid className={styles.DayGrid}>
            <Calendar.DayGridHeader className={styles.DayGridHeader}>
              <Calendar.DayGridHeaderRow className={styles.DayGridHeaderRow}>
                {(day) => (
                  <Calendar.DayGridHeaderCell
                    value={day}
                    key={day.getTime()}
                    className={styles.DayGridHeaderCell}
                  />
                )}
              </Calendar.DayGridHeaderRow>
            </Calendar.DayGridHeader>
            <Calendar.DayGridBody className={styles.DayGridBody}>
              {(week) => (
                <Calendar.DayGridRow
                  value={week}
                  key={week.getTime()}
                  className={styles.DayGridRow}
                >
                  {(day) => (
                    <Calendar.DayGridCell
                      value={day}
                      key={day.getTime()}
                      className={styles.DayGridCell}
                    >
                      <Calendar.DayButton className={styles.DayButton} />
                    </Calendar.DayGridCell>
                  )}
                </Calendar.DayGridRow>
              )}
            </Calendar.DayGridBody>
          </Calendar.DayGrid>
        </React.Fragment>
      )}
    </Calendar.Root>
  );
}
```

```css
/* calendar.module.css */
.Root {
  border: 1px solid var(--calendar-root-border-color);
  border-radius: 8px;
  height: 312px;
  display: flex;
  flex-direction: column;

  --calendar-root-color: var(--color-gray-900);
  --calendar-root-border-color: var(--color-gray-500);
  --calendar-button-hover-bg-color: #e0f2fe;
  --calendar-button-focus-border-color: #0ea5e9;
  --calendar-day-grid-separator-bg-color: #9ca3af;
  --calendar-day-grid-header-color: var(--color-gray-500);

  --calendar-cell-selected-bg-color: #7dd3fc;
  --calendar-cell-outside-month-color: var(--color-gray-400);
  --calendar-cell-disabled-color: var(--color-gray-500);
  --calendar-cell-current-border-color: var(--color-gray-500);
  --calendar-cell-unavailable-color: #f87171;

  @media (prefers-color-scheme: dark) {
    --calendar-button-hover-bg-color: #075985;
    --calendar-cell-selected-bg-color: #0369a1;
  }
}

.Header {
  box-sizing: border-box;
  padding: 8px 12px;
  height: 40px;
  display: flex;
  justify-content: space-between;
  align-items: center;
}

.HeaderLabel {
  color: var(--calendar-root-color);
}

.DecrementMonth,
.IncrementMonth {
  border: none;
  user-select: none;
  background-color: transparent;
  border-radius: 4px;
  color: var(--calendar-root-color);
  margin: 0 6px;
  padding: 0;
  display: flex;

  &:hover {
    background-color: var(--calendar-button-hover-bg-color);
  }

  &:focus-visible {
    outline: 2px solid var(--calendar-button-focus-border-color);
  }

  &[data-disabled] {
    pointer-events: none;
    color: var(--calendar-cell-disabled-color);
  }
}

.DayGrid {
  padding: 12px;
  height: 276px;
  display: flex;
  flex-direction: column;
  gap: 4px;
  z-index: 1;
  position: relative;
}

.DayGridBody {
  display: flex;
  flex-direction: column;
  row-gap: 2px;
}

.DayGridRow,
.DayGridHeaderRow {
  display: flex;
  justify-content: center;
}

.DayGridHeaderCell {
  width: 36px;
  text-align: center;
  font-size: 0.75rem;
  color: var(--calendar-day-grid-header-color);
  font-weight: 500;
  padding: 0;
}

.DayGridCell {
  padding: 0;
}

.DayButton {
  background: none;
  padding: 0;
  font: inherit;
  height: 36px;
  width: 36px;
  position: relative;
  user-select: none;
  border: none;
  background-color: transparent;
  outline: none;
  box-sizing: border-box;
  border-radius: 4px;
  color: var(--calendar-root-color);

  &::before {
    content: '';
    position: absolute;
    inset: 2px;
    border-radius: 4px;
    border: none;
    z-index: -1;
    background-color: transparent;
  }

  &::after {
    content: '';
    border-radius: 4px;
    position: absolute;
    inset: 2px;
  }

  &:not([data-outside-month]):focus-visible {
    &::after {
      outline: 2px solid var(--calendar-button-focus-border-color);
    }
  }

  &:not([data-selected]):hover::before {
    background-color: var(--calendar-button-hover-bg-color);
  }

  &[data-selected]:not([data-outside-month])::before {
    background-color: var(--calendar-cell-selected-bg-color);
  }

  &[data-disabled] {
    pointer-events: none;
  }

  &:not([data-outside-month])[data-disabled] {
    color: var(--calendar-cell-disabled-color);
  }

  &:not([data-outside-month])[data-invalid] {
    text-decoration: line-through;
  }

  &[data-outside-month] {
    color: var(--calendar-cell-outside-month-color);
    pointer-events: none;
  }

  &[data-current]:not([data-selected], :focus-visible)::after {
    outline: 1px solid var(--calendar-cell-current-border-color);
  }

  &:not([data-disabled], [data-outside-month])[data-unavailable] {
    text-decoration: line-through;
    color: var(--calendar-cell-unavailable-color);
  }
}
```

### Lazy loading

You can render custom content inside `<Calendar.DayButton>` and use `Calendar.useContext()` to
react to month changes — for example, to lazily load per-day data.

## Demo

### CSS Modules

This example shows how to implement the component using CSS Modules.

```css
/* index.module.css */
.Root {
  height: 420px;
}

.DayGrid {
  height: 384px;
}

.DayGridHeaderCell {
  width: 44px;
}

.DayButton {
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  height: 52px;
  width: 44px;
  gap: 2px;
}

.DayNumber {
  font-size: 0.9375rem;
  font-weight: 400;
  line-height: 1;
}

.Price {
  font-size: 0.625rem;
  line-height: 1;
  color: var(--color-gray-500);

  @media (prefers-reduced-motion: no-preference) {
    animation: fadeIn 0.2s ease both;
  }
}

.PriceDeal {
  color: #16a34a;
  font-weight: 600;

  @media (prefers-color-scheme: dark) {
    color: #4ade80;
  }
}

.PriceSoldOut {
  font-size: 0.625rem;
  line-height: 1;
  color: var(--color-gray-400);

  @media (prefers-reduced-motion: no-preference) {
    animation: fadeIn 0.2s ease both;
  }
}

.Skeleton {
  display: block;
  width: 24px;
  height: 10px;
  border-radius: 2px;
  background: linear-gradient(
    90deg,
    var(--color-gray-200) 25%,
    var(--color-gray-100) 50%,
    var(--color-gray-200) 75%
  );
  background-size: 200% 100%;

  @media (prefers-color-scheme: dark) {
    background: linear-gradient(
      90deg,
      var(--color-gray-700) 25%,
      var(--color-gray-600) 50%,
      var(--color-gray-700) 75%
    );
    background-size: 200% 100%;
  }

  @media (prefers-reduced-motion: no-preference) {
    animation: shimmer 1.5s ease-in-out infinite;
  }
}

@keyframes shimmer {
  0% {
    background-position: 200% 0;
  }

  100% {
    background-position: -200% 0;
  }
}

@keyframes fadeIn {
  from {
    opacity: 0;
  }

  to {
    opacity: 1;
  }
}
```

```tsx
/* index.tsx */
'use client';
import * as React from 'react';
import clsx from 'clsx';
import { format } from 'date-fns/format';
import { Calendar } from '@base-ui/react/calendar';
import { useTimeout } from '@base-ui/utils/useTimeout';
import { ChevronLeft, ChevronRight } from 'lucide-react';
import styles from './calendar.module.css';
import indexStyles from './index.module.css';

function DayPrice({
  loading,
  price,
  isDeal,
  loadingDelay,
  revealDelay,
}: {
  loading: boolean;
  price: number | null | undefined;
  isDeal: boolean;
  loadingDelay: string;
  revealDelay: string;
}) {
  if (loading) {
    return (
      <span
        className={indexStyles.Skeleton}
        style={{ animationDelay: loadingDelay }}
        aria-hidden="true"
      />
    );
  }
  if (price != null) {
    return (
      <span
        className={clsx(indexStyles.Price, isDeal && indexStyles.PriceDeal)}
        style={{ animationDelay: revealDelay }}
      >
        ${price}
      </span>
    );
  }
  return (
    <span className={indexStyles.PriceSoldOut} style={{ animationDelay: revealDelay }}>
      —
    </span>
  );
}

function CalendarContent() {
  const { visibleDate } = Calendar.useContext();
  const [prices, setPrices] = React.useState<Record<string, number | null>>({});
  const [loading, setLoading] = React.useState(true);
  const timeout = useTimeout();

  const monthKey = format(visibleDate, 'yyyy-MM');

  React.useEffect(() => {
    const year = parseInt(monthKey.split('-')[0], 10);
    const month = parseInt(monthKey.split('-')[1], 10) - 1;
    setLoading(true);
    timeout.start(800, () => {
      setPrices((prev) => ({ ...prev, ...generateMonthPrices(year, month) }));
      setLoading(false);
    });
  }, [monthKey, timeout]);

  const minPrice = React.useMemo(() => {
    const monthPrices = Object.entries(prices)
      .filter(([key]) => key.startsWith(monthKey))
      .flatMap(([, p]) => (p != null ? [p] : []));
    return monthPrices.length > 0 ? Math.min(...monthPrices) : null;
  }, [prices, monthKey]);

  return (
    <React.Fragment>
      <header className={styles.Header}>
        <Calendar.DecrementMonth className={styles.DecrementMonth}>
          <ChevronLeft />
        </Calendar.DecrementMonth>
        <span className={styles.HeaderLabel}>{format(visibleDate, 'MMMM yyyy')}</span>
        <Calendar.IncrementMonth className={styles.IncrementMonth}>
          <ChevronRight />
        </Calendar.IncrementMonth>
      </header>
      <Calendar.DayGrid className={clsx(styles.DayGrid, indexStyles.DayGrid)}>
        <Calendar.DayGridHeader className={styles.DayGridHeader}>
          <Calendar.DayGridHeaderRow className={styles.DayGridHeaderRow}>
            {(day) => (
              <Calendar.DayGridHeaderCell
                value={day}
                key={day.getTime()}
                className={clsx(styles.DayGridHeaderCell, indexStyles.DayGridHeaderCell)}
              />
            )}
          </Calendar.DayGridHeaderRow>
        </Calendar.DayGridHeader>
        <Calendar.DayGridBody className={styles.DayGridBody}>
          {(week) => (
            <Calendar.DayGridRow value={week} key={week.getTime()} className={styles.DayGridRow}>
              {(day) => {
                const dateKey = format(day, 'yyyy-MM-dd');
                const inCurrentMonth = dateKey.startsWith(monthKey);
                const price = prices[dateKey];
                const isDeal = inCurrentMonth && price != null && price === minPrice;
                const daySeed =
                  day.getFullYear() * 10000 + (day.getMonth() + 1) * 100 + day.getDate();
                const loadingDelay = `${(seededRandom(daySeed + 50) * 0.4).toFixed(3)}s`;
                const revealDelay = `${(seededRandom(daySeed + 60) * 0.5).toFixed(3)}s`;
                return (
                  <Calendar.DayGridCell
                    value={day}
                    key={day.getTime()}
                    className={styles.DayGridCell}
                  >
                    <Calendar.DayButton className={clsx(styles.DayButton, indexStyles.DayButton)}>
                      <span className={indexStyles.DayNumber}>{format(day, 'd')}</span>
                      {inCurrentMonth && (
                        <DayPrice
                          loading={loading}
                          price={price}
                          isDeal={isDeal}
                          loadingDelay={loadingDelay}
                          revealDelay={revealDelay}
                        />
                      )}
                    </Calendar.DayButton>
                  </Calendar.DayGridCell>
                );
              }}
            </Calendar.DayGridRow>
          )}
        </Calendar.DayGridBody>
      </Calendar.DayGrid>
    </React.Fragment>
  );
}

export default function FlightPriceCalendar() {
  return (
    <Calendar.Root
      className={clsx(styles.Root, indexStyles.Root)}
      aria-label="Flight departure date"
    >
      <CalendarContent />
    </Calendar.Root>
  );
}

function seededRandom(seed: number) {
  const x = Math.sin(seed) * 10000;
  return x - Math.floor(x);
}

function generateMonthPrices(year: number, month: number): Record<string, number | null> {
  const prices: Record<string, number | null> = {};
  const daysInMonth = new Date(year, month + 1, 0).getDate();
  for (let d = 1; d <= daysInMonth; d += 1) {
    const date = new Date(year, month, d);
    const dateKey = format(date, 'yyyy-MM-dd');
    const seed = year * 10000 + (month + 1) * 100 + d;
    const rand = seededRandom(seed);
    prices[dateKey] = rand < 0.15 ? null : Math.floor(79 + seededRandom(seed + 1) * 320);
  }
  return prices;
}
```

```css
/* calendar.module.css */
.Root {
  border: 1px solid var(--calendar-root-border-color);
  border-radius: 8px;
  height: 312px;
  display: flex;
  flex-direction: column;

  --calendar-root-color: var(--color-gray-900);
  --calendar-root-border-color: var(--color-gray-500);
  --calendar-button-hover-bg-color: #e0f2fe;
  --calendar-button-focus-border-color: #0ea5e9;
  --calendar-day-grid-separator-bg-color: #9ca3af;
  --calendar-day-grid-header-color: var(--color-gray-500);

  --calendar-cell-selected-bg-color: #7dd3fc;
  --calendar-cell-outside-month-color: var(--color-gray-400);
  --calendar-cell-disabled-color: var(--color-gray-500);
  --calendar-cell-current-border-color: var(--color-gray-500);
  --calendar-cell-unavailable-color: #f87171;

  @media (prefers-color-scheme: dark) {
    --calendar-button-hover-bg-color: #075985;
    --calendar-cell-selected-bg-color: #0369a1;
  }
}

.Header {
  box-sizing: border-box;
  padding: 8px 12px;
  height: 40px;
  display: flex;
  justify-content: space-between;
  align-items: center;
}

.HeaderLabel {
  color: var(--calendar-root-color);
}

.DecrementMonth,
.IncrementMonth {
  border: none;
  user-select: none;
  background-color: transparent;
  border-radius: 4px;
  color: var(--calendar-root-color);
  margin: 0 6px;
  padding: 0;
  display: flex;

  &:hover {
    background-color: var(--calendar-button-hover-bg-color);
  }

  &:focus-visible {
    outline: 2px solid var(--calendar-button-focus-border-color);
  }

  &[data-disabled] {
    pointer-events: none;
    color: var(--calendar-cell-disabled-color);
  }
}

.DayGrid {
  padding: 12px;
  height: 276px;
  display: flex;
  flex-direction: column;
  gap: 4px;
  z-index: 1;
  position: relative;
}

.DayGridBody {
  display: flex;
  flex-direction: column;
  row-gap: 2px;
}

.DayGridRow,
.DayGridHeaderRow {
  display: flex;
  justify-content: center;
}

.DayGridHeaderCell {
  width: 36px;
  text-align: center;
  font-size: 0.75rem;
  color: var(--calendar-day-grid-header-color);
  font-weight: 500;
  padding: 0;
}

.DayGridCell {
  padding: 0;
}

.DayButton {
  background: none;
  padding: 0;
  font: inherit;
  height: 36px;
  width: 36px;
  position: relative;
  user-select: none;
  border: none;
  background-color: transparent;
  outline: none;
  box-sizing: border-box;
  border-radius: 4px;
  color: var(--calendar-root-color);

  &::before {
    content: '';
    position: absolute;
    inset: 2px;
    border-radius: 4px;
    border: none;
    z-index: -1;
    background-color: transparent;
  }

  &::after {
    content: '';
    border-radius: 4px;
    position: absolute;
    inset: 2px;
  }

  &:not([data-outside-month]):focus-visible {
    &::after {
      outline: 2px solid var(--calendar-button-focus-border-color);
    }
  }

  &:not([data-selected]):hover::before {
    background-color: var(--calendar-button-hover-bg-color);
  }

  &[data-selected]:not([data-outside-month])::before {
    background-color: var(--calendar-cell-selected-bg-color);
  }

  &[data-disabled] {
    pointer-events: none;
  }

  &:not([data-outside-month])[data-disabled] {
    color: var(--calendar-cell-disabled-color);
  }

  &:not([data-outside-month])[data-invalid] {
    text-decoration: line-through;
  }

  &[data-outside-month] {
    color: var(--calendar-cell-outside-month-color);
    pointer-events: none;
  }

  &[data-current]:not([data-selected], :focus-visible)::after {
    outline: 1px solid var(--calendar-cell-current-border-color);
  }

  &:not([data-disabled], [data-outside-month])[data-unavailable] {
    text-decoration: line-through;
    color: var(--calendar-cell-unavailable-color);
  }
}
```

### Display week numbers

You can use `useWeekList()` and `useDayList()` hooks to build a calendar with additional DOM elements, like week numbers.

## Demo

### CSS Modules

This example shows how to implement the component using CSS Modules.

```css
/* index.module.css */
.DayWeekNumber {
  height: 36px;
  width: 36px;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  font-size: 0.75rem;
  color: var(--calendar-day-grid-header-color);
  /* Avoid the bold front weight from the th element */
  font-weight: 500;
  padding: 0;
}
```

```tsx
/* index.tsx */
'use client';
import * as React from 'react';
import { format } from 'date-fns/format';
import { startOfWeek } from 'date-fns/startOfWeek';
import { startOfMonth } from 'date-fns/startOfMonth';
import { getWeek } from 'date-fns/getWeek';
import { Calendar } from '@base-ui/react/calendar';
import { ChevronLeft, ChevronRight } from 'lucide-react';
import styles from './calendar.module.css';
import indexStyles from './index.module.css';

export default function CalendarWithWeekNumbers() {
  const getWeekList = Calendar.useWeekList();
  const getDayList = Calendar.useDayList();

  return (
    <Calendar.Root className={styles.Root}>
      {({ visibleDate }) => (
        <React.Fragment>
          <header className={styles.Header}>
            <Calendar.DecrementMonth className={styles.DecrementMonth}>
              <ChevronLeft />
            </Calendar.DecrementMonth>
            <span className={styles.HeaderLabel}>{format(visibleDate, 'MMMM yyyy')}</span>
            <Calendar.IncrementMonth className={styles.IncrementMonth}>
              <ChevronRight />
            </Calendar.IncrementMonth>
          </header>
          <Calendar.DayGrid className={styles.DayGrid}>
            <Calendar.DayGridHeader className={styles.DayGridHeader}>
              <Calendar.DayGridHeaderRow className={styles.DayGridHeaderRow}>
                <th
                  role="columnheader"
                  aria-label="Week number"
                  className={styles.DayGridHeaderCell}
                >
                  #
                </th>
                {getDayList({ date: startOfWeek(new Date()), amount: 7 }).map((day) => (
                  <Calendar.DayGridHeaderCell
                    value={day}
                    key={day.getTime()}
                    className={styles.DayGridHeaderCell}
                  />
                ))}
              </Calendar.DayGridHeaderRow>
            </Calendar.DayGridHeader>
            <Calendar.DayGridBody className={styles.DayGridBody}>
              {getWeekList({
                date: startOfMonth(visibleDate),
                amount: 'end-of-month',
              }).map((week) => (
                <Calendar.DayGridRow
                  value={week}
                  key={week.getTime()}
                  className={styles.DayGridRow}
                >
                  <th
                    className={indexStyles.DayWeekNumber}
                    scope="row"
                    aria-label={`Week ${getWeek(week)}`}
                  >
                    {getWeek(week)}
                  </th>
                  {getDayList({ date: week, amount: 7 }).map((day) => (
                    <Calendar.DayGridCell
                      value={day}
                      key={day.getTime()}
                      className={styles.DayGridCell}
                    >
                      <Calendar.DayButton className={styles.DayButton} />
                    </Calendar.DayGridCell>
                  ))}
                </Calendar.DayGridRow>
              ))}
            </Calendar.DayGridBody>
          </Calendar.DayGrid>
        </React.Fragment>
      )}
    </Calendar.Root>
  );
}
```

```css
/* calendar.module.css */
.Root {
  border: 1px solid var(--calendar-root-border-color);
  border-radius: 8px;
  height: 312px;
  display: flex;
  flex-direction: column;

  --calendar-root-color: var(--color-gray-900);
  --calendar-root-border-color: var(--color-gray-500);
  --calendar-button-hover-bg-color: #e0f2fe;
  --calendar-button-focus-border-color: #0ea5e9;
  --calendar-day-grid-separator-bg-color: #9ca3af;
  --calendar-day-grid-header-color: var(--color-gray-500);

  --calendar-cell-selected-bg-color: #7dd3fc;
  --calendar-cell-outside-month-color: var(--color-gray-400);
  --calendar-cell-disabled-color: var(--color-gray-500);
  --calendar-cell-current-border-color: var(--color-gray-500);
  --calendar-cell-unavailable-color: #f87171;

  @media (prefers-color-scheme: dark) {
    --calendar-button-hover-bg-color: #075985;
    --calendar-cell-selected-bg-color: #0369a1;
  }
}

.Header {
  box-sizing: border-box;
  padding: 8px 12px;
  height: 40px;
  display: flex;
  justify-content: space-between;
  align-items: center;
}

.HeaderLabel {
  color: var(--calendar-root-color);
}

.DecrementMonth,
.IncrementMonth {
  border: none;
  user-select: none;
  background-color: transparent;
  border-radius: 4px;
  color: var(--calendar-root-color);
  margin: 0 6px;
  padding: 0;
  display: flex;

  &:hover {
    background-color: var(--calendar-button-hover-bg-color);
  }

  &:focus-visible {
    outline: 2px solid var(--calendar-button-focus-border-color);
  }

  &[data-disabled] {
    pointer-events: none;
    color: var(--calendar-cell-disabled-color);
  }
}

.DayGrid {
  padding: 12px;
  height: 276px;
  display: flex;
  flex-direction: column;
  gap: 4px;
  z-index: 1;
  position: relative;
}

.DayGridBody {
  display: flex;
  flex-direction: column;
  row-gap: 2px;
}

.DayGridRow,
.DayGridHeaderRow {
  display: flex;
  justify-content: center;
}

.DayGridHeaderCell {
  width: 36px;
  text-align: center;
  font-size: 0.75rem;
  color: var(--calendar-day-grid-header-color);
  font-weight: 500;
  padding: 0;
}

.DayGridCell {
  padding: 0;
}

.DayButton {
  background: none;
  padding: 0;
  font: inherit;
  height: 36px;
  width: 36px;
  position: relative;
  user-select: none;
  border: none;
  background-color: transparent;
  outline: none;
  box-sizing: border-box;
  border-radius: 4px;
  color: var(--calendar-root-color);

  &::before {
    content: '';
    position: absolute;
    inset: 2px;
    border-radius: 4px;
    border: none;
    z-index: -1;
    background-color: transparent;
  }

  &::after {
    content: '';
    border-radius: 4px;
    position: absolute;
    inset: 2px;
  }

  &:not([data-outside-month]):focus-visible {
    &::after {
      outline: 2px solid var(--calendar-button-focus-border-color);
    }
  }

  &:not([data-selected]):hover::before {
    background-color: var(--calendar-button-hover-bg-color);
  }

  &[data-selected]:not([data-outside-month])::before {
    background-color: var(--calendar-cell-selected-bg-color);
  }

  &[data-disabled] {
    pointer-events: none;
  }

  &:not([data-outside-month])[data-disabled] {
    color: var(--calendar-cell-disabled-color);
  }

  &:not([data-outside-month])[data-invalid] {
    text-decoration: line-through;
  }

  &[data-outside-month] {
    color: var(--calendar-cell-outside-month-color);
    pointer-events: none;
  }

  &[data-current]:not([data-selected], :focus-visible)::after {
    outline: 1px solid var(--calendar-cell-current-border-color);
  }

  &:not([data-disabled], [data-outside-month])[data-unavailable] {
    text-decoration: line-through;
    color: var(--calendar-cell-unavailable-color);
  }
}
```

### Timezone support

You can use the `timezone` prop to display data in the calendar in your preferred timezone.

Timezone support is based on [@date-fns/tz](https://github.com/date-fns/tz).

## Demo

### CSS Modules

This example shows how to implement the component using CSS Modules.

```css
/* index.module.css */
.Wrapper {
  display: flex;
  flex-flow: row wrap;
  justify-content: center;
}

.Text {
  color: var(--color-gray-900);
  width: 100%;
  text-align: center;
}
```

```tsx
/* index.tsx */
'use client';
import * as React from 'react';
import { format } from 'date-fns/format';
import { TZDate } from '@date-fns/tz';
import { Calendar } from '@base-ui/react/calendar';
import { ChevronLeft, ChevronRight } from 'lucide-react';
import styles from './calendar.module.css';
import indexStyles from './index.module.css';

export default function CalendarWithTimezone() {
  const [value, setValue] = React.useState<Date | null>(
    new TZDate(2025, 3, 17, 4, 45, 0, 0, 'Europe/Paris'),
  );
  return (
    <div className={indexStyles.Wrapper}>
      <p className={indexStyles.Text}>
        Calendar is displayed in <strong>America/New_York</strong> timezone.
      </p>
      <Calendar.Root
        className={styles.Root}
        timezone="America/New_York"
        value={value}
        onValueChange={setValue}
      >
        {({ visibleDate }) => (
          <React.Fragment>
            <header className={styles.Header}>
              <Calendar.DecrementMonth className={styles.DecrementMonth}>
                <ChevronLeft />
              </Calendar.DecrementMonth>
              <span className={styles.HeaderLabel}>{format(visibleDate, 'MMMM yyyy')}</span>
              <Calendar.IncrementMonth className={styles.IncrementMonth}>
                <ChevronRight />
              </Calendar.IncrementMonth>
            </header>
            <Calendar.DayGrid className={styles.DayGrid}>
              <Calendar.DayGridHeader className={styles.DayGridHeader}>
                <Calendar.DayGridHeaderRow className={styles.DayGridHeaderRow}>
                  {(day) => (
                    <Calendar.DayGridHeaderCell
                      value={day}
                      key={day.getTime()}
                      className={styles.DayGridHeaderCell}
                    />
                  )}
                </Calendar.DayGridHeaderRow>
              </Calendar.DayGridHeader>
              <Calendar.DayGridBody className={styles.DayGridBody}>
                {(week) => (
                  <Calendar.DayGridRow
                    value={week}
                    key={week.getTime()}
                    className={styles.DayGridRow}
                  >
                    {(day) => (
                      <Calendar.DayGridCell
                        value={day}
                        key={day.getTime()}
                        className={styles.DayGridCell}
                      >
                        <Calendar.DayButton className={styles.DayButton} />
                      </Calendar.DayGridCell>
                    )}
                  </Calendar.DayGridRow>
                )}
              </Calendar.DayGridBody>
            </Calendar.DayGrid>
          </React.Fragment>
        )}
      </Calendar.Root>
      {value && <p className={indexStyles.Text}>Stored date: {value.toString()}</p>}
    </div>
  );
}
```

```css
/* calendar.module.css */
.Root {
  border: 1px solid var(--calendar-root-border-color);
  border-radius: 8px;
  height: 312px;
  display: flex;
  flex-direction: column;

  --calendar-root-color: var(--color-gray-900);
  --calendar-root-border-color: var(--color-gray-500);
  --calendar-button-hover-bg-color: #e0f2fe;
  --calendar-button-focus-border-color: #0ea5e9;
  --calendar-day-grid-separator-bg-color: #9ca3af;
  --calendar-day-grid-header-color: var(--color-gray-500);

  --calendar-cell-selected-bg-color: #7dd3fc;
  --calendar-cell-outside-month-color: var(--color-gray-400);
  --calendar-cell-disabled-color: var(--color-gray-500);
  --calendar-cell-current-border-color: var(--color-gray-500);
  --calendar-cell-unavailable-color: #f87171;

  @media (prefers-color-scheme: dark) {
    --calendar-button-hover-bg-color: #075985;
    --calendar-cell-selected-bg-color: #0369a1;
  }
}

.Header {
  box-sizing: border-box;
  padding: 8px 12px;
  height: 40px;
  display: flex;
  justify-content: space-between;
  align-items: center;
}

.HeaderLabel {
  color: var(--calendar-root-color);
}

.DecrementMonth,
.IncrementMonth {
  border: none;
  user-select: none;
  background-color: transparent;
  border-radius: 4px;
  color: var(--calendar-root-color);
  margin: 0 6px;
  padding: 0;
  display: flex;

  &:hover {
    background-color: var(--calendar-button-hover-bg-color);
  }

  &:focus-visible {
    outline: 2px solid var(--calendar-button-focus-border-color);
  }

  &[data-disabled] {
    pointer-events: none;
    color: var(--calendar-cell-disabled-color);
  }
}

.DayGrid {
  padding: 12px;
  height: 276px;
  display: flex;
  flex-direction: column;
  gap: 4px;
  z-index: 1;
  position: relative;
}

.DayGridBody {
  display: flex;
  flex-direction: column;
  row-gap: 2px;
}

.DayGridRow,
.DayGridHeaderRow {
  display: flex;
  justify-content: center;
}

.DayGridHeaderCell {
  width: 36px;
  text-align: center;
  font-size: 0.75rem;
  color: var(--calendar-day-grid-header-color);
  font-weight: 500;
  padding: 0;
}

.DayGridCell {
  padding: 0;
}

.DayButton {
  background: none;
  padding: 0;
  font: inherit;
  height: 36px;
  width: 36px;
  position: relative;
  user-select: none;
  border: none;
  background-color: transparent;
  outline: none;
  box-sizing: border-box;
  border-radius: 4px;
  color: var(--calendar-root-color);

  &::before {
    content: '';
    position: absolute;
    inset: 2px;
    border-radius: 4px;
    border: none;
    z-index: -1;
    background-color: transparent;
  }

  &::after {
    content: '';
    border-radius: 4px;
    position: absolute;
    inset: 2px;
  }

  &:not([data-outside-month]):focus-visible {
    &::after {
      outline: 2px solid var(--calendar-button-focus-border-color);
    }
  }

  &:not([data-selected]):hover::before {
    background-color: var(--calendar-button-hover-bg-color);
  }

  &[data-selected]:not([data-outside-month])::before {
    background-color: var(--calendar-cell-selected-bg-color);
  }

  &[data-disabled] {
    pointer-events: none;
  }

  &:not([data-outside-month])[data-disabled] {
    color: var(--calendar-cell-disabled-color);
  }

  &:not([data-outside-month])[data-invalid] {
    text-decoration: line-through;
  }

  &[data-outside-month] {
    color: var(--calendar-cell-outside-month-color);
    pointer-events: none;
  }

  &[data-current]:not([data-selected], :focus-visible)::after {
    outline: 1px solid var(--calendar-cell-current-border-color);
  }

  &:not([data-disabled], [data-outside-month])[data-unavailable] {
    text-decoration: line-through;
    color: var(--calendar-cell-unavailable-color);
  }
}
```

The calendar can display the date in a selected timezone.

## Demo

### CSS Modules

This example shows how to implement the component using CSS Modules.

```css
/* index.module.css */
.Wrapper {
  display: flex;
  flex-flow: column wrap;
  gap: 16px;
}

.Select {
  box-sizing: border-box;
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 0.75rem;
  height: 2.5rem;
  padding-left: 0.875rem;
  padding-right: 0.75rem;
  margin: 0;
  outline: 0;
  border: 1px solid var(--color-gray-200);
  border-radius: 0.375rem;
  background-color: canvas;
  font-family: inherit;
  font-size: 1rem;
  line-height: 1.5rem;
  color: var(--color-gray-900);
  -webkit-user-select: none;
  user-select: none;
  min-width: 10rem;

  @media (hover: hover) {
    &:hover {
      background-color: var(--color-gray-100);
    }
  }

  &[data-popup-open] {
    background-color: var(--color-gray-100);
  }

  &:focus-visible {
    outline: 2px solid var(--color-blue);
    outline-offset: -1px;
  }
}

.Positioner {
  outline: none;
  z-index: 1;
  -webkit-user-select: none;
  user-select: none;
}

.Popup {
  box-sizing: border-box;
  border-radius: 0.375rem;
  background-color: canvas;
  background-clip: padding-box;
  color: var(--color-gray-900);
  min-width: var(--anchor-width);
  transform-origin: var(--transform-origin);
  transition:
    transform 150ms,
    opacity 150ms;

  &[data-starting-style],
  &[data-ending-style] {
    opacity: 0;
    transform: scale(0.9);
  }

  &[data-side='none'] {
    transition: none;
    transform: none;
    opacity: 1;
    min-width: calc(var(--anchor-width) + 1rem);
  }

  @media (prefers-color-scheme: light) {
    outline: 1px solid var(--color-gray-200);
    box-shadow:
      0 10px 15px -3px var(--color-gray-200),
      0 4px 6px -4px var(--color-gray-200);
  }

  @media (prefers-color-scheme: dark) {
    outline: 1px solid var(--color-gray-300);
  }
}

.List {
  box-sizing: border-box;
  position: relative;
  padding-block: 0.25rem;
  overflow-y: auto;
  max-height: var(--available-height);
  scroll-padding-block: 1.5rem;
}

.Item {
  box-sizing: border-box;
  outline: 0;
  font-size: 0.875rem;
  line-height: 1rem;
  padding-block: 0.5rem;
  padding-left: 0.625rem;
  padding-right: 1rem;
  display: grid;
  gap: 0.5rem;
  align-items: center;
  grid-template-columns: 0.75rem 1fr;
  cursor: default;
  -webkit-user-select: none;
  user-select: none;

  @media (pointer: coarse) {
    padding-block: 0.625rem;
    font-size: 0.925rem;
  }

  [data-side='none'] & {
    font-size: 1rem;
    padding-right: 3rem;
  }

  &[data-highlighted] {
    z-index: 0;
    position: relative;
    color: var(--color-gray-50);
  }

  &[data-highlighted]::before {
    content: '';
    z-index: -1;
    position: absolute;
    inset-block: 0;
    inset-inline: 0.25rem;
    border-radius: 0.25rem;
    background-color: var(--color-gray-900);
  }
}

.ItemIndicator {
  grid-column-start: 1;
}

.ItemText {
  grid-column-start: 2;
}
```

```tsx
/* index.tsx */
'use client';
import * as React from 'react';
import { format } from 'date-fns/format';
import { Calendar } from '@base-ui/react/calendar';
import { Select } from '@base-ui/react';
import { ChevronLeft, ChevronRight } from 'lucide-react';
import styles from './calendar.module.css';
import indexStyles from './index.module.css';

export default function CalendarWithTimezoneDisplay() {
  const [timezone, setTimezone] = React.useState<Timezone | null>('Australia/Sydney');
  return (
    <div className={indexStyles.Wrapper}>
      <TimezoneSelect
        value={timezone}
        onValueChange={(value) => setTimezone(value as Timezone | null)}
      />
      <Calendar.Root className={styles.Root} timezone={timezone ?? undefined}>
        {({ visibleDate }) => (
          <React.Fragment>
            <header className={styles.Header}>
              <Calendar.DecrementMonth className={styles.DecrementMonth}>
                <ChevronLeft />
              </Calendar.DecrementMonth>
              <span className={styles.HeaderLabel}>{format(visibleDate, 'MMMM yyyy')}</span>
              <Calendar.IncrementMonth className={styles.IncrementMonth}>
                <ChevronRight />
              </Calendar.IncrementMonth>
            </header>
            <Calendar.DayGrid className={styles.DayGrid}>
              <Calendar.DayGridHeader className={styles.DayGridHeader}>
                <Calendar.DayGridHeaderRow className={styles.DayGridHeaderRow}>
                  {(day) => (
                    <Calendar.DayGridHeaderCell
                      value={day}
                      key={day.getTime()}
                      className={styles.DayGridHeaderCell}
                    />
                  )}
                </Calendar.DayGridHeaderRow>
              </Calendar.DayGridHeader>
              <Calendar.DayGridBody className={styles.DayGridBody}>
                {(week) => (
                  <Calendar.DayGridRow
                    value={week}
                    key={week.getTime()}
                    className={styles.DayGridRow}
                  >
                    {(day) => (
                      <Calendar.DayGridCell
                        value={day}
                        key={day.getTime()}
                        className={styles.DayGridCell}
                      >
                        <Calendar.DayButton className={styles.DayButton} />
                      </Calendar.DayGridCell>
                    )}
                  </Calendar.DayGridRow>
                )}
              </Calendar.DayGridBody>
            </Calendar.DayGrid>
          </React.Fragment>
        )}
      </Calendar.Root>
    </div>
  );
}

function TimezoneSelect(props: Omit<Select.Root.Props<string, false>, 'children'>) {
  return (
    <Select.Root {...props}>
      <Select.Trigger className={indexStyles.Select}>
        <Select.Value className={indexStyles.Value} placeholder="Select timezone" />
        <Select.Icon>↓</Select.Icon>
      </Select.Trigger>
      <Select.Portal>
        <Select.Positioner className={indexStyles.Positioner} sideOffset={8}>
          <Select.Popup className={indexStyles.Popup}>
            <Select.List className={indexStyles.List}>
              {timezones.map((timezone) => (
                <Select.Item key={timezone} value={timezone} className={indexStyles.Item}>
                  <Select.ItemIndicator className={indexStyles.ItemIndicator}>
                    ✔️
                  </Select.ItemIndicator>
                  <Select.ItemText className={indexStyles.ItemText}>{timezone}</Select.ItemText>
                </Select.Item>
              ))}
            </Select.List>
          </Select.Popup>
        </Select.Positioner>
      </Select.Portal>
    </Select.Root>
  );
}

type Timezone = (typeof timezones)[number];

const timezones = [
  'America/Los_Angeles',
  'Europe/Paris',
  'Asia/Tokyo',
  'Australia/Sydney',
] as const;
```

```css
/* calendar.module.css */
.Root {
  border: 1px solid var(--calendar-root-border-color);
  border-radius: 8px;
  height: 312px;
  display: flex;
  flex-direction: column;

  --calendar-root-color: var(--color-gray-900);
  --calendar-root-border-color: var(--color-gray-500);
  --calendar-button-hover-bg-color: #e0f2fe;
  --calendar-button-focus-border-color: #0ea5e9;
  --calendar-day-grid-separator-bg-color: #9ca3af;
  --calendar-day-grid-header-color: var(--color-gray-500);

  --calendar-cell-selected-bg-color: #7dd3fc;
  --calendar-cell-outside-month-color: var(--color-gray-400);
  --calendar-cell-disabled-color: var(--color-gray-500);
  --calendar-cell-current-border-color: var(--color-gray-500);
  --calendar-cell-unavailable-color: #f87171;

  @media (prefers-color-scheme: dark) {
    --calendar-button-hover-bg-color: #075985;
    --calendar-cell-selected-bg-color: #0369a1;
  }
}

.Header {
  box-sizing: border-box;
  padding: 8px 12px;
  height: 40px;
  display: flex;
  justify-content: space-between;
  align-items: center;
}

.HeaderLabel {
  color: var(--calendar-root-color);
}

.DecrementMonth,
.IncrementMonth {
  border: none;
  user-select: none;
  background-color: transparent;
  border-radius: 4px;
  color: var(--calendar-root-color);
  margin: 0 6px;
  padding: 0;
  display: flex;

  &:hover {
    background-color: var(--calendar-button-hover-bg-color);
  }

  &:focus-visible {
    outline: 2px solid var(--calendar-button-focus-border-color);
  }

  &[data-disabled] {
    pointer-events: none;
    color: var(--calendar-cell-disabled-color);
  }
}

.DayGrid {
  padding: 12px;
  height: 276px;
  display: flex;
  flex-direction: column;
  gap: 4px;
  z-index: 1;
  position: relative;
}

.DayGridBody {
  display: flex;
  flex-direction: column;
  row-gap: 2px;
}

.DayGridRow,
.DayGridHeaderRow {
  display: flex;
  justify-content: center;
}

.DayGridHeaderCell {
  width: 36px;
  text-align: center;
  font-size: 0.75rem;
  color: var(--calendar-day-grid-header-color);
  font-weight: 500;
  padding: 0;
}

.DayGridCell {
  padding: 0;
}

.DayButton {
  background: none;
  padding: 0;
  font: inherit;
  height: 36px;
  width: 36px;
  position: relative;
  user-select: none;
  border: none;
  background-color: transparent;
  outline: none;
  box-sizing: border-box;
  border-radius: 4px;
  color: var(--calendar-root-color);

  &::before {
    content: '';
    position: absolute;
    inset: 2px;
    border-radius: 4px;
    border: none;
    z-index: -1;
    background-color: transparent;
  }

  &::after {
    content: '';
    border-radius: 4px;
    position: absolute;
    inset: 2px;
  }

  &:not([data-outside-month]):focus-visible {
    &::after {
      outline: 2px solid var(--calendar-button-focus-border-color);
    }
  }

  &:not([data-selected]):hover::before {
    background-color: var(--calendar-button-hover-bg-color);
  }

  &[data-selected]:not([data-outside-month])::before {
    background-color: var(--calendar-cell-selected-bg-color);
  }

  &[data-disabled] {
    pointer-events: none;
  }

  &:not([data-outside-month])[data-disabled] {
    color: var(--calendar-cell-disabled-color);
  }

  &:not([data-outside-month])[data-invalid] {
    text-decoration: line-through;
  }

  &[data-outside-month] {
    color: var(--calendar-cell-outside-month-color);
    pointer-events: none;
  }

  &[data-current]:not([data-selected], :focus-visible)::after {
    outline: 1px solid var(--calendar-cell-current-border-color);
  }

  &:not([data-disabled], [data-outside-month])[data-unavailable] {
    text-decoration: line-through;
    color: var(--calendar-cell-unavailable-color);
  }
}
```

### Month and year select

Use `Calendar.useContext()` inside the calendar to read `visibleDate` and call `setVisibleDate`, enabling custom controls like `<select>` dropdowns for direct month and year navigation.

## Demo

### CSS Modules

This example shows how to implement the component using CSS Modules.

```css
/* index.module.css */
.Select {
  box-sizing: border-box;
  height: 2rem;
  padding-inline: 0.1rem;
  border: 1px solid var(--color-gray-200);
  border-radius: 0.375rem;
  background-color: canvas;
  font-family: inherit;
  font-size: 0.875rem;
  color: var(--color-gray-900);
}
```

```tsx
/* index.tsx */
'use client';
import * as React from 'react';
import { format } from 'date-fns/format';
import { getMonth } from 'date-fns/getMonth';
import { getYear } from 'date-fns/getYear';
import { Calendar } from '@base-ui/react/calendar';
import { ChevronLeft, ChevronRight } from 'lucide-react';
import styles from './calendar.module.css';
import indexStyles from './index.module.css';

const YEAR_PAST = 40;
const YEAR_FUTURE = 10;
const MONTHS = Array.from({ length: 12 }, (_, i) => format(new Date(2000, i, 1), 'MMMM'));

function CalendarContent() {
  const { visibleDate, setVisibleDate } = Calendar.useContext();
  const currentMonth = getMonth(visibleDate);
  const currentYear = getYear(visibleDate);
  const todayYear = getYear(new Date());
  const years = Array.from(
    { length: YEAR_PAST + YEAR_FUTURE + 1 },
    (_, i) => todayYear - YEAR_PAST + i,
  );

  return (
    <React.Fragment>
      <header className={styles.Header}>
        <Calendar.DecrementMonth className={styles.DecrementMonth}>
          <ChevronLeft />
        </Calendar.DecrementMonth>
        <select
          className={indexStyles.Select}
          value={currentMonth}
          onChange={(event) =>
            setVisibleDate(
              new Date(currentYear, Number(event.target.value), 1),
              event.nativeEvent,
              event.target,
              'month-change',
            )
          }
        >
          {MONTHS.map((name, index) => (
            <option key={name} value={index}>
              {name}
            </option>
          ))}
        </select>
        <select
          className={indexStyles.Select}
          value={currentYear}
          onChange={(event) =>
            setVisibleDate(
              new Date(Number(event.target.value), currentMonth, 1),
              event.nativeEvent,
              event.target,
              'month-change',
            )
          }
        >
          {years.map((year) => (
            <option key={year} value={year}>
              {year}
            </option>
          ))}
        </select>
        <Calendar.IncrementMonth className={styles.IncrementMonth}>
          <ChevronRight />
        </Calendar.IncrementMonth>
      </header>
      <Calendar.DayGrid className={styles.DayGrid}>
        <Calendar.DayGridHeader className={styles.DayGridHeader}>
          <Calendar.DayGridHeaderRow className={styles.DayGridHeaderRow}>
            {(day) => (
              <Calendar.DayGridHeaderCell
                value={day}
                key={day.getTime()}
                className={styles.DayGridHeaderCell}
              />
            )}
          </Calendar.DayGridHeaderRow>
        </Calendar.DayGridHeader>
        <Calendar.DayGridBody className={styles.DayGridBody}>
          {(week) => (
            <Calendar.DayGridRow value={week} key={week.getTime()} className={styles.DayGridRow}>
              {(day) => (
                <Calendar.DayGridCell
                  value={day}
                  key={day.getTime()}
                  className={styles.DayGridCell}
                >
                  <Calendar.DayButton className={styles.DayButton} />
                </Calendar.DayGridCell>
              )}
            </Calendar.DayGridRow>
          )}
        </Calendar.DayGridBody>
      </Calendar.DayGrid>
    </React.Fragment>
  );
}

export default function ExampleCalendarYearMonthSelect() {
  return (
    <Calendar.Root className={styles.Root}>
      <CalendarContent />
    </Calendar.Root>
  );
}
```

```css
/* calendar.module.css */
.Root {
  border: 1px solid var(--calendar-root-border-color);
  border-radius: 8px;
  height: 312px;
  display: flex;
  flex-direction: column;

  --calendar-root-color: var(--color-gray-900);
  --calendar-root-border-color: var(--color-gray-500);
  --calendar-button-hover-bg-color: #e0f2fe;
  --calendar-button-focus-border-color: #0ea5e9;
  --calendar-day-grid-separator-bg-color: #9ca3af;
  --calendar-day-grid-header-color: var(--color-gray-500);

  --calendar-cell-selected-bg-color: #7dd3fc;
  --calendar-cell-outside-month-color: var(--color-gray-400);
  --calendar-cell-disabled-color: var(--color-gray-500);
  --calendar-cell-current-border-color: var(--color-gray-500);
  --calendar-cell-unavailable-color: #f87171;

  @media (prefers-color-scheme: dark) {
    --calendar-button-hover-bg-color: #075985;
    --calendar-cell-selected-bg-color: #0369a1;
  }
}

.Header {
  box-sizing: border-box;
  padding: 8px 12px;
  height: 40px;
  display: flex;
  justify-content: space-between;
  align-items: center;
}

.HeaderLabel {
  color: var(--calendar-root-color);
}

.DecrementMonth,
.IncrementMonth {
  border: none;
  user-select: none;
  background-color: transparent;
  border-radius: 4px;
  color: var(--calendar-root-color);
  margin: 0 6px;
  padding: 0;
  display: flex;

  &:hover {
    background-color: var(--calendar-button-hover-bg-color);
  }

  &:focus-visible {
    outline: 2px solid var(--calendar-button-focus-border-color);
  }

  &[data-disabled] {
    pointer-events: none;
    color: var(--calendar-cell-disabled-color);
  }
}

.DayGrid {
  padding: 12px;
  height: 276px;
  display: flex;
  flex-direction: column;
  gap: 4px;
  z-index: 1;
  position: relative;
}

.DayGridBody {
  display: flex;
  flex-direction: column;
  row-gap: 2px;
}

.DayGridRow,
.DayGridHeaderRow {
  display: flex;
  justify-content: center;
}

.DayGridHeaderCell {
  width: 36px;
  text-align: center;
  font-size: 0.75rem;
  color: var(--calendar-day-grid-header-color);
  font-weight: 500;
  padding: 0;
}

.DayGridCell {
  padding: 0;
}

.DayButton {
  background: none;
  padding: 0;
  font: inherit;
  height: 36px;
  width: 36px;
  position: relative;
  user-select: none;
  border: none;
  background-color: transparent;
  outline: none;
  box-sizing: border-box;
  border-radius: 4px;
  color: var(--calendar-root-color);

  &::before {
    content: '';
    position: absolute;
    inset: 2px;
    border-radius: 4px;
    border: none;
    z-index: -1;
    background-color: transparent;
  }

  &::after {
    content: '';
    border-radius: 4px;
    position: absolute;
    inset: 2px;
  }

  &:not([data-outside-month]):focus-visible {
    &::after {
      outline: 2px solid var(--calendar-button-focus-border-color);
    }
  }

  &:not([data-selected]):hover::before {
    background-color: var(--calendar-button-hover-bg-color);
  }

  &[data-selected]:not([data-outside-month])::before {
    background-color: var(--calendar-cell-selected-bg-color);
  }

  &[data-disabled] {
    pointer-events: none;
  }

  &:not([data-outside-month])[data-disabled] {
    color: var(--calendar-cell-disabled-color);
  }

  &:not([data-outside-month])[data-invalid] {
    text-decoration: line-through;
  }

  &[data-outside-month] {
    color: var(--calendar-cell-outside-month-color);
    pointer-events: none;
  }

  &[data-current]:not([data-selected], :focus-visible)::after {
    outline: 1px solid var(--calendar-cell-current-border-color);
  }

  &:not([data-disabled], [data-outside-month])[data-unavailable] {
    text-decoration: line-through;
    color: var(--calendar-cell-unavailable-color);
  }
}
```

### Animating month changes

You can use the `<Calendar.Viewport>` component to animate month transitions.
Wrapping the `<Calendar.DayGridBody>` in the `<Calendar.Viewport>` renders the previous month if a transition is in progress.

## Demo

### CSS Modules

This example shows how to implement the component using CSS Modules.

```css
/* index.module.css */
.Root {
  /* Clip overflow to prevent content spilling out when transitioning. */
  overflow: clip;
  --duration: 0.2s;
  --easing: cubic-bezier(0.22, 1, 0.36, 1);
}

.HeaderLabelWrapper {
  width: 100%;
  display: grid;
  justify-content: center;
}

.HeaderLabel {
  grid-row: 1;
  grid-column: 1;
  background-color: var(--color-background);
  min-width: 185px;
  text-align: center;
  overflow: clip;
  /* Apply transition only when `data-ending-style` is present to allow for smooth animations from original translated positions. */
  &[data-ending-style] {
    &[data-current],
    &[data-previous] {
      @media (prefers-reduced-motion: no-preference) {
        transition:
          opacity var(--duration) var(--easing),
          transform var(--duration) var(--easing);
      }
    }
  }

  &[data-navigation-direction='next'][data-previous] {
    &[data-starting-style] {
      opacity: 1;
      transform: translateY(0);
    }
    &[data-ending-style] {
      opacity: 0;
      transform: translateY(-20px);
    }
  }

  &[data-navigation-direction='next'][data-current] {
    &[data-starting-style] {
      opacity: 0;
      transform: translateY(20px);
    }
    &[data-ending-style] {
      opacity: 1;
      transform: translateY(0);
    }
  }

  &[data-navigation-direction='previous'][data-previous] {
    &[data-starting-style] {
      opacity: 1;
      transform: translateY(0);
    }
    &[data-ending-style] {
      opacity: 0;
      transform: translateY(20px);
    }
  }

  &[data-navigation-direction='previous'][data-current] {
    &[data-starting-style] {
      opacity: 0;
      transform: translateY(-20px);
    }
    &[data-ending-style] {
      opacity: 1;
      transform: translateY(0);
    }
  }
}

.DayGrid {
  /* Need to use grid to position two tbody elements centered horizontally. */
  display: grid;
  grid-template-rows: min-content 1fr;
  grid-template-columns: 1fr;
}

.DayGridBody {
  grid-row: 2;
  grid-column: 1;
  transform: translateX(0);
  opacity: 1;

  /* Apply transition only when `data-ending-style` is present to allow for smooth animations from original translated positions. */
  &[data-ending-style] {
    &[data-current],
    &[data-previous] {
      @media (prefers-reduced-motion: no-preference) {
        transition:
          opacity var(--duration) var(--easing),
          transform var(--duration) var(--easing);
      }
    }
  }

  &[data-navigation-direction='next'][data-previous] {
    &[data-starting-style] {
      transform: translateX(0);
      opacity: 1;
    }
    &[data-ending-style] {
      transform: translateX(-100%);
      opacity: 0;
    }
  }

  &[data-navigation-direction='next'][data-current] {
    &[data-starting-style] {
      transform: translateX(100%);
      opacity: 0;
    }
    &[data-ending-style] {
      transform: translateX(0);
      opacity: 1;
    }
  }

  &[data-navigation-direction='previous'][data-previous] {
    &[data-starting-style] {
      transform: translateX(0);
      opacity: 1;
    }
    &[data-ending-style] {
      transform: translateX(100%);
      opacity: 0;
    }
  }

  &[data-navigation-direction='previous'][data-current] {
    &[data-starting-style] {
      transform: translateX(-100%);
      opacity: 0;
    }
    &[data-ending-style] {
      transform: translateX(0);
      opacity: 1;
    }
  }
}
```

```tsx
/* index.tsx */
'use client';
import * as React from 'react';
import clsx from 'clsx';
import { format } from 'date-fns/format';
import { Calendar } from '@base-ui/react/calendar';
import { ChevronLeft, ChevronRight } from 'lucide-react';
import styles from './calendar.module.css';
import indexStyles from './index.module.css';

export default function AnimatedCalendar() {
  return (
    <Calendar.Root className={clsx(styles.Root, indexStyles.Root)}>
      {({ visibleDate }) => (
        <React.Fragment>
          <header className={styles.Header}>
            <Calendar.DecrementMonth className={styles.DecrementMonth}>
              <ChevronLeft />
            </Calendar.DecrementMonth>
            <div className={indexStyles.HeaderLabelWrapper}>
              <Calendar.Viewport>
                <span className={clsx(styles.HeaderLabel, indexStyles.HeaderLabel)}>
                  {format(visibleDate, 'MMMM yyyy')}
                </span>
              </Calendar.Viewport>
            </div>
            <Calendar.IncrementMonth className={styles.IncrementMonth}>
              <ChevronRight />
            </Calendar.IncrementMonth>
          </header>
          <Calendar.DayGrid className={clsx(styles.DayGrid, indexStyles.DayGrid)}>
            <Calendar.DayGridHeader className={styles.DayGridHeader}>
              <Calendar.DayGridHeaderRow className={styles.DayGridHeaderRow}>
                {(day) => (
                  <Calendar.DayGridHeaderCell
                    value={day}
                    key={day.getTime()}
                    className={styles.DayGridHeaderCell}
                  />
                )}
              </Calendar.DayGridHeaderRow>
            </Calendar.DayGridHeader>
            <Calendar.Viewport>
              <Calendar.DayGridBody className={clsx(styles.DayGridBody, indexStyles.DayGridBody)}>
                {(week) => (
                  <Calendar.DayGridRow
                    value={week}
                    key={week.getTime()}
                    className={styles.DayGridRow}
                  >
                    {(day) => (
                      <Calendar.DayGridCell
                        value={day}
                        key={day.getTime()}
                        className={styles.DayGridCell}
                      >
                        <Calendar.DayButton className={styles.DayButton} />
                      </Calendar.DayGridCell>
                    )}
                  </Calendar.DayGridRow>
                )}
              </Calendar.DayGridBody>
            </Calendar.Viewport>
          </Calendar.DayGrid>
        </React.Fragment>
      )}
    </Calendar.Root>
  );
}
```

```css
/* calendar.module.css */
.Root {
  border: 1px solid var(--calendar-root-border-color);
  border-radius: 8px;
  height: 312px;
  display: flex;
  flex-direction: column;

  --calendar-root-color: var(--color-gray-900);
  --calendar-root-border-color: var(--color-gray-500);
  --calendar-button-hover-bg-color: #e0f2fe;
  --calendar-button-focus-border-color: #0ea5e9;
  --calendar-day-grid-separator-bg-color: #9ca3af;
  --calendar-day-grid-header-color: var(--color-gray-500);

  --calendar-cell-selected-bg-color: #7dd3fc;
  --calendar-cell-outside-month-color: var(--color-gray-400);
  --calendar-cell-disabled-color: var(--color-gray-500);
  --calendar-cell-current-border-color: var(--color-gray-500);
  --calendar-cell-unavailable-color: #f87171;

  @media (prefers-color-scheme: dark) {
    --calendar-button-hover-bg-color: #075985;
    --calendar-cell-selected-bg-color: #0369a1;
  }
}

.Header {
  box-sizing: border-box;
  padding: 8px 12px;
  height: 40px;
  display: flex;
  justify-content: space-between;
  align-items: center;
}

.HeaderLabel {
  color: var(--calendar-root-color);
}

.DecrementMonth,
.IncrementMonth {
  border: none;
  user-select: none;
  background-color: transparent;
  border-radius: 4px;
  color: var(--calendar-root-color);
  margin: 0 6px;
  padding: 0;
  display: flex;

  &:hover {
    background-color: var(--calendar-button-hover-bg-color);
  }

  &:focus-visible {
    outline: 2px solid var(--calendar-button-focus-border-color);
  }

  &[data-disabled] {
    pointer-events: none;
    color: var(--calendar-cell-disabled-color);
  }
}

.DayGrid {
  padding: 12px;
  height: 276px;
  display: flex;
  flex-direction: column;
  gap: 4px;
  z-index: 1;
  position: relative;
}

.DayGridBody {
  display: flex;
  flex-direction: column;
  row-gap: 2px;
}

.DayGridRow,
.DayGridHeaderRow {
  display: flex;
  justify-content: center;
}

.DayGridHeaderCell {
  width: 36px;
  text-align: center;
  font-size: 0.75rem;
  color: var(--calendar-day-grid-header-color);
  font-weight: 500;
  padding: 0;
}

.DayGridCell {
  padding: 0;
}

.DayButton {
  background: none;
  padding: 0;
  font: inherit;
  height: 36px;
  width: 36px;
  position: relative;
  user-select: none;
  border: none;
  background-color: transparent;
  outline: none;
  box-sizing: border-box;
  border-radius: 4px;
  color: var(--calendar-root-color);

  &::before {
    content: '';
    position: absolute;
    inset: 2px;
    border-radius: 4px;
    border: none;
    z-index: -1;
    background-color: transparent;
  }

  &::after {
    content: '';
    border-radius: 4px;
    position: absolute;
    inset: 2px;
  }

  &:not([data-outside-month]):focus-visible {
    &::after {
      outline: 2px solid var(--calendar-button-focus-border-color);
    }
  }

  &:not([data-selected]):hover::before {
    background-color: var(--calendar-button-hover-bg-color);
  }

  &[data-selected]:not([data-outside-month])::before {
    background-color: var(--calendar-cell-selected-bg-color);
  }

  &[data-disabled] {
    pointer-events: none;
  }

  &:not([data-outside-month])[data-disabled] {
    color: var(--calendar-cell-disabled-color);
  }

  &:not([data-outside-month])[data-invalid] {
    text-decoration: line-through;
  }

  &[data-outside-month] {
    color: var(--calendar-cell-outside-month-color);
    pointer-events: none;
  }

  &[data-current]:not([data-selected], :focus-visible)::after {
    outline: 1px solid var(--calendar-cell-current-border-color);
  }

  &:not([data-disabled], [data-outside-month])[data-unavailable] {
    text-decoration: line-through;
    color: var(--calendar-cell-unavailable-color);
  }
}
```

#### Animating with `motion/react`

You can use external animation libraries like [Framer Motion](https://www.framer.com/motion/) to animate month transitions.

## Demo

### CSS Modules

This example shows how to implement the component using CSS Modules.

```tsx
/* index.tsx */
'use client';
import * as React from 'react';
import { format } from 'date-fns/format';
import { Calendar } from '@base-ui/react/calendar';
import { motion, AnimatePresence } from 'motion/react';
import { ChevronLeft, ChevronRight } from 'lucide-react';
import styles from './calendar.module.css';

export default function AnimatedCalendarWithMotion() {
  return (
    <Calendar.Root className={styles.Root}>
      {({ visibleDate }) => (
        <React.Fragment>
          <header className={styles.Header}>
            <Calendar.DecrementMonth className={styles.DecrementMonth}>
              <ChevronLeft />
            </Calendar.DecrementMonth>
            <AnimatePresence initial={false} mode="popLayout">
              <motion.span layout className={styles.HeaderLabel}>
                {format(visibleDate, 'MMMM yyyy')}
              </motion.span>
            </AnimatePresence>
            <Calendar.IncrementMonth className={styles.IncrementMonth}>
              <ChevronRight />
            </Calendar.IncrementMonth>
          </header>
          <Calendar.DayGrid className={styles.DayGrid}>
            <Calendar.DayGridHeader className={styles.DayGridHeader}>
              <Calendar.DayGridHeaderRow className={styles.DayGridHeaderRow}>
                {(day) => (
                  <Calendar.DayGridHeaderCell
                    value={day}
                    key={day.getTime()}
                    className={styles.DayGridHeaderCell}
                  />
                )}
              </Calendar.DayGridHeaderRow>
            </Calendar.DayGridHeader>
            <AnimatePresence initial={false} mode="popLayout">
              <Calendar.DayGridBody
                key={`${visibleDate.getUTCFullYear()}-${visibleDate.getMonth()}`}
                className={styles.DayGridBody}
                render={
                  <motion.tbody
                    initial={{ opacity: 0, scale: 0 }}
                    animate={{ opacity: 1, scale: 1 }}
                    exit={{ opacity: 0, scale: 0 }}
                  />
                }
              >
                {(week) => (
                  <Calendar.DayGridRow
                    value={week}
                    key={week.getTime()}
                    className={styles.DayGridRow}
                  >
                    {(day) => (
                      <Calendar.DayGridCell
                        value={day}
                        key={day.getTime()}
                        className={styles.DayGridCell}
                      >
                        <Calendar.DayButton className={styles.DayButton} />
                      </Calendar.DayGridCell>
                    )}
                  </Calendar.DayGridRow>
                )}
              </Calendar.DayGridBody>
            </AnimatePresence>
          </Calendar.DayGrid>
        </React.Fragment>
      )}
    </Calendar.Root>
  );
}
```

```css
/* calendar.module.css */
.Root {
  border: 1px solid var(--calendar-root-border-color);
  border-radius: 8px;
  height: 312px;
  display: flex;
  flex-direction: column;

  --calendar-root-color: var(--color-gray-900);
  --calendar-root-border-color: var(--color-gray-500);
  --calendar-button-hover-bg-color: #e0f2fe;
  --calendar-button-focus-border-color: #0ea5e9;
  --calendar-day-grid-separator-bg-color: #9ca3af;
  --calendar-day-grid-header-color: var(--color-gray-500);

  --calendar-cell-selected-bg-color: #7dd3fc;
  --calendar-cell-outside-month-color: var(--color-gray-400);
  --calendar-cell-disabled-color: var(--color-gray-500);
  --calendar-cell-current-border-color: var(--color-gray-500);
  --calendar-cell-unavailable-color: #f87171;

  @media (prefers-color-scheme: dark) {
    --calendar-button-hover-bg-color: #075985;
    --calendar-cell-selected-bg-color: #0369a1;
  }
}

.Header {
  box-sizing: border-box;
  padding: 8px 12px;
  height: 40px;
  display: flex;
  justify-content: space-between;
  align-items: center;
}

.HeaderLabel {
  color: var(--calendar-root-color);
}

.DecrementMonth,
.IncrementMonth {
  border: none;
  user-select: none;
  background-color: transparent;
  border-radius: 4px;
  color: var(--calendar-root-color);
  margin: 0 6px;
  padding: 0;
  display: flex;

  &:hover {
    background-color: var(--calendar-button-hover-bg-color);
  }

  &:focus-visible {
    outline: 2px solid var(--calendar-button-focus-border-color);
  }

  &[data-disabled] {
    pointer-events: none;
    color: var(--calendar-cell-disabled-color);
  }
}

.DayGrid {
  padding: 12px;
  height: 276px;
  display: flex;
  flex-direction: column;
  gap: 4px;
  z-index: 1;
  position: relative;
}

.DayGridBody {
  display: flex;
  flex-direction: column;
  row-gap: 2px;
}

.DayGridRow,
.DayGridHeaderRow {
  display: flex;
  justify-content: center;
}

.DayGridHeaderCell {
  width: 36px;
  text-align: center;
  font-size: 0.75rem;
  color: var(--calendar-day-grid-header-color);
  font-weight: 500;
  padding: 0;
}

.DayGridCell {
  padding: 0;
}

.DayButton {
  background: none;
  padding: 0;
  font: inherit;
  height: 36px;
  width: 36px;
  position: relative;
  user-select: none;
  border: none;
  background-color: transparent;
  outline: none;
  box-sizing: border-box;
  border-radius: 4px;
  color: var(--calendar-root-color);

  &::before {
    content: '';
    position: absolute;
    inset: 2px;
    border-radius: 4px;
    border: none;
    z-index: -1;
    background-color: transparent;
  }

  &::after {
    content: '';
    border-radius: 4px;
    position: absolute;
    inset: 2px;
  }

  &:not([data-outside-month]):focus-visible {
    &::after {
      outline: 2px solid var(--calendar-button-focus-border-color);
    }
  }

  &:not([data-selected]):hover::before {
    background-color: var(--calendar-button-hover-bg-color);
  }

  &[data-selected]:not([data-outside-month])::before {
    background-color: var(--calendar-cell-selected-bg-color);
  }

  &[data-disabled] {
    pointer-events: none;
  }

  &:not([data-outside-month])[data-disabled] {
    color: var(--calendar-cell-disabled-color);
  }

  &:not([data-outside-month])[data-invalid] {
    text-decoration: line-through;
  }

  &[data-outside-month] {
    color: var(--calendar-cell-outside-month-color);
    pointer-events: none;
  }

  &[data-current]:not([data-selected], :focus-visible)::after {
    outline: 1px solid var(--calendar-cell-current-border-color);
  }

  &:not([data-disabled], [data-outside-month])[data-unavailable] {
    text-decoration: line-through;
    color: var(--calendar-cell-unavailable-color);
  }
}
```

### Localization

The calendar's locale is controlled by the date library. It handles month and day names, as well as the start of the week.
You can use the [`<LocalizationProvider>`](/react/utils/localization-provider.md) to customize these settings.

## API reference

### Root

Groups all parts of the calendar.
Renders a `<div>` element.

**Root Props:**

| Prop                                    | Type                                                                                                       | Default                                                                                 | Description                                                                                                                                                                                                                    |
| :-------------------------------------- | :--------------------------------------------------------------------------------------------------------- | :-------------------------------------------------------------------------------------- | :----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| defaultValue                            | `TemporalValue`                                                                                            | -                                                                                       | The uncontrolled value that should be initially selected.&#xA;To render a controlled (Range)Calendar, use the `value` prop instead.                                                                                            |
| value                                   | `TemporalValue`                                                                                            | -                                                                                       | The controlled value that should be selected.&#xA;To render an uncontrolled (Range)Calendar, use the `defaultValue` prop instead.                                                                                              |
| onValueChange                           | `((value: TemporalValue, eventDetails: CalendarValueChangeEventDetails<ValidateDateReturnValue>) => void)` | -                                                                                       | Event handler called when the selected value changes.&#xA;Provides the new value as an argument.&#xA;Has `getValidationError()` in the `eventDetails` to retrieve the validation error associated to the new value.            |
| defaultVisibleDate                      | `Date`                                                                                                     | -                                                                                       | The date used to decide which month should be initially displayed in the Day Grid.&#xA;To render a controlled Calendar, use the `visibleDate` prop instead.                                                                    |
| visibleDate                             | `Date`                                                                                                     | -                                                                                       | The date used to decide which month should be displayed in the Day Grid.&#xA;To render an uncontrolled Calendar, use the `defaultVisibleDate` prop instead.                                                                    |
| onVisibleDateChange                     | `((visibleDate: Date, eventDetails: CalendarVisibleDateChangeEventDetails) => void)`                       | -                                                                                       | Event handler called when the visible date changes.&#xA;Provides the new date as an argument.&#xA;Has the change reason in the `eventDetails`.                                                                                 |
| isDateUnavailable                       | `((day: Date) => boolean)`                                                                                 | -                                                                                       | Mark specific dates as unavailable.&#xA;Those dates will not be selectable but they will still be focusable with the keyboard.                                                                                                 |
| maxDate                                 | `Date`                                                                                                     | -                                                                                       | Maximal selectable date.                                                                                                                                                                                                       |
| minDate                                 | `Date`                                                                                                     | -                                                                                       | Minimal selectable date.                                                                                                                                                                                                       |
| monthPageSize                           | `number`                                                                                                   | `1`                                                                                     | The amount of months to move by when navigating.&#xA;This is mostly useful when displaying multiple day grids.                                                                                                                 |
| referenceDate                           | `Date`                                                                                                     | `'The closest valid date using the validation props.'`                                  | The date used to generate the new value when both `value` and `defaultValue` are empty.&#xA;It can be used to:\* set a desired time on the selected date;                                                                      |
| \* set a desired default year or month; |
| timezone                                | `string`                                                                                                   | `'The timezone of the "value" or "defaultValue" prop if defined, "default" otherwise.'` | Choose which timezone to use for the value.&#xA;Example: "default", "system", "UTC", "America/New_York".&#xA;If you pass values from other timezones to some props, they will be converted to this timezone before being used. |
| disabled                                | `boolean`                                                                                                  | `false`                                                                                 | Whether the component should ignore user interaction.                                                                                                                                                                          |
| readOnly                                | `boolean`                                                                                                  | `false`                                                                                 | Whether the user should be unable to select a date in the calendar.                                                                                                                                                            |
| invalid                                 | `boolean`                                                                                                  | `false`                                                                                 | Whether the calendar is forcefully marked as invalid.&#xA;A calendar can be invalid when the selected date fails validation (i.e., is outside of the allowed `minDate` and `maxDate` range).                                   |
| children                                | `ReactNode \| ((parameters: CalendarContext) => ReactNode)`                                                | -                                                                                       | The children of the component.&#xA;If a function is provided, it will be called with the public context as its parameter.                                                                                                      |
| className                               | `string \| ((state: Calendar.Root.State) => string \| undefined)`                                          | -                                                                                       | CSS class applied to the element, or a function that&#xA;returns a class based on the component’s state.                                                                                                                       |
| style                                   | `CSSProperties \| ((state: Calendar.Root.State) => CSSProperties \| undefined)`                            | -                                                                                       | -                                                                                                                                                                                                                              |
| render                                  | `ReactElement \| ((props: HTMLProps, state: Calendar.Root.State) => ReactElement)`                         | -                                                                                       | Allows you to replace the component’s HTML element&#xA;with a different tag, or compose it with another component.Accepts a `ReactElement` or a function that returns the element to render.                                   |

**Root Data Attributes:**

| Attribute                 | Type                             | Description                                                                   |
| :------------------------ | :------------------------------- | :---------------------------------------------------------------------------- |
| data-disabled             | -                                | Present when the calendar is disabled.                                        |
| data-readonly             | -                                | Present when the calendar is readonly.                                        |
| data-invalid              | -                                | Present when the current value is invalid (fails validation).                 |
| data-empty                | -                                | Present when the current value is empty.                                      |
| data-navigation-direction | `'previous' \| 'next' \| 'none'` | Indicates the direction of the navigation (based on the month navigating to). |

### DayGrid

Groups all the parts of the calendar's day grid.
Renders a `<table>` element.

**DayGrid Props:**

| Prop      | Type                                                                                  | Default | Description                                                                                                                                                                                  |
| :-------- | :------------------------------------------------------------------------------------ | :------ | :------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| className | `string \| ((state: Calendar.DayGrid.State) => string \| undefined)`                  | -       | CSS class applied to the element, or a function that&#xA;returns a class based on the component’s state.                                                                                     |
| style     | `CSSProperties \| ((state: Calendar.DayGrid.State) => CSSProperties \| undefined)`    | -       | -                                                                                                                                                                                            |
| render    | `ReactElement \| ((props: HTMLProps, state: Calendar.DayGrid.State) => ReactElement)` | -       | Allows you to replace the component’s HTML element&#xA;with a different tag, or compose it with another component.Accepts a `ReactElement` or a function that returns the element to render. |

### DayGridHeader

Groups all parts of the calendar's day grid header.
Renders a `<thead>` element.

**DayGridHeader Props:**

| Prop      | Type                                                                                        | Default | Description                                                                                                                                                                                  |
| :-------- | :------------------------------------------------------------------------------------------ | :------ | :------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| className | `string \| ((state: Calendar.DayGridHeader.State) => string \| undefined)`                  | -       | CSS class applied to the element, or a function that&#xA;returns a class based on the component’s state.                                                                                     |
| style     | `CSSProperties \| ((state: Calendar.DayGridHeader.State) => CSSProperties \| undefined)`    | -       | -                                                                                                                                                                                            |
| render    | `ReactElement \| ((props: HTMLProps, state: Calendar.DayGridHeader.State) => ReactElement)` | -       | Allows you to replace the component’s HTML element&#xA;with a different tag, or compose it with another component.Accepts a `ReactElement` or a function that returns the element to render. |

### DayGridHeaderCell

An individual day header cell in the calendar.
Renders a `<th>` element.

**DayGridHeaderCell Props:**

| Prop      | Type                                                                                            | Default                                                         | Description                                                                                                                                                                                  |
| :-------- | :---------------------------------------------------------------------------------------------- | :-------------------------------------------------------------- | :------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| value     | `Date`                                                                                          | -                                                               | -                                                                                                                                                                                            |
| formatter | `((date: Date) => string)`                                                                      | `(date) => adapter.format(date, 'EEE').charAt(0).toUpperCase()` | The formatter function used to display the day of the week.                                                                                                                                  |
| className | `string \| ((state: Calendar.DayGridHeaderCell.State) => string \| undefined)`                  | -                                                               | CSS class applied to the element, or a function that&#xA;returns a class based on the component’s state.                                                                                     |
| style     | `CSSProperties \| ((state: Calendar.DayGridHeaderCell.State) => CSSProperties \| undefined)`    | -                                                               | \*                                                                                                                                                                                           |
| render    | `ReactElement \| ((props: HTMLProps, state: Calendar.DayGridHeaderCell.State) => ReactElement)` | -                                                               | Allows you to replace the component’s HTML element&#xA;with a different tag, or compose it with another component.Accepts a `ReactElement` or a function that returns the element to render. |

### DayGridBody

Groups all parts of the calendar's day grid.
Renders a `<tbody>` element.

**DayGridBody Props:**

| Prop            | Type                                                                                      | Default | Description                                                                                                                                                                                  |
| :-------------- | :---------------------------------------------------------------------------------------- | :------ | :------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| fixedWeekNumber | `number`                                                                                  | -       | Will render the requested amount of weeks by adding weeks of the next month if needed.&#xA;Set it to 6 to create a Gregorian calendar where all months have the same number of weeks.        |
| offset          | `number`                                                                                  | `0`     | The offset to apply to the rendered month compared to the current month.&#xA;This is mostly useful when displaying multiple day grids.                                                       |
| children        | `ReactNode \| ((week: Date, index: number, weeks: Date[]) => ReactNode)`                  | -       | The children of the component.&#xA;If a function is provided, it will be called for each week to render as its parameter.                                                                    |
| className       | `string \| ((state: Calendar.DayGridBody.State) => string \| undefined)`                  | -       | CSS class applied to the element, or a function that&#xA;returns a class based on the component’s state.                                                                                     |
| style           | `CSSProperties \| ((state: Calendar.DayGridBody.State) => CSSProperties \| undefined)`    | -       | -                                                                                                                                                                                            |
| render          | `ReactElement \| ((props: HTMLProps, state: Calendar.DayGridBody.State) => ReactElement)` | -       | Allows you to replace the component’s HTML element&#xA;with a different tag, or compose it with another component.Accepts a `ReactElement` or a function that returns the element to render. |

### DayGridRow

Groups all cells of a given calendar's day grid row.
Renders a `<tr>` element.

**DayGridRow Props:**

| Prop      | Type                                                                                     | Default | Description                                                                                                                                                                                  |
| :-------- | :--------------------------------------------------------------------------------------- | :------ | :------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| value     | `Date`                                                                                   | -       | The date object representing the week.                                                                                                                                                       |
| children  | `ReactNode \| ((day: Date, index: number, days: Date[]) => ReactNode)`                   | -       | The children of the component.&#xA;If a function is provided, it will be called for each day of the week as its parameter.                                                                   |
| className | `string \| ((state: Calendar.DayGridRow.State) => string \| undefined)`                  | -       | CSS class applied to the element, or a function that&#xA;returns a class based on the component’s state.                                                                                     |
| style     | `CSSProperties \| ((state: Calendar.DayGridRow.State) => CSSProperties \| undefined)`    | -       | -                                                                                                                                                                                            |
| render    | `ReactElement \| ((props: HTMLProps, state: Calendar.DayGridRow.State) => ReactElement)` | -       | Allows you to replace the component’s HTML element&#xA;with a different tag, or compose it with another component.Accepts a `ReactElement` or a function that returns the element to render. |

### DayGridCell

An individual day cell in the calendar.
Renders a `<td>` element.

**DayGridCell Props:**

| Prop      | Type                                                                                      | Default | Description                                                                                                                                                                                  |
| :-------- | :---------------------------------------------------------------------------------------- | :------ | :------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| value     | `Date`                                                                                    | -       | The value to select when this cell is clicked.                                                                                                                                               |
| className | `string \| ((state: Calendar.DayGridCell.State) => string \| undefined)`                  | -       | CSS class applied to the element, or a function that&#xA;returns a class based on the component’s state.                                                                                     |
| style     | `CSSProperties \| ((state: Calendar.DayGridCell.State) => CSSProperties \| undefined)`    | -       | -                                                                                                                                                                                            |
| render    | `ReactElement \| ((props: HTMLProps, state: Calendar.DayGridCell.State) => ReactElement)` | -       | Allows you to replace the component’s HTML element&#xA;with a different tag, or compose it with another component.Accepts a `ReactElement` or a function that returns the element to render. |

### DayButton

An individual interactive day button in the calendar.
Renders a `<button>` element.

**DayButton Props:**

| Prop                  | Type                                                                                    | Default                      | Description                                                                                                                                                                                  |
| :-------------------- | :-------------------------------------------------------------------------------------- | :--------------------------- | :------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| focusableWhenDisabled | `boolean`                                                                               | `false`                      | When `true` the item remains focusable when disabled.                                                                                                                                        |
| nativeButton          | `boolean`                                                                               | `true`                       | Whether the component renders a native `<button>` element when replacing it&#xA;via the `render` prop.&#xA;Set to `false` if the rendered element is not a button (e.g. `<div>`).            |
| format                | `string`                                                                                | `adapter.formats.dayOfMonth` | The format used to display the day.                                                                                                                                                          |
| className             | `string \| ((state: Calendar.DayButton.State) => string \| undefined)`                  | -                            | CSS class applied to the element, or a function that&#xA;returns a class based on the component’s state.                                                                                     |
| style                 | `CSSProperties \| ((state: Calendar.DayButton.State) => CSSProperties \| undefined)`    | -                            | -                                                                                                                                                                                            |
| render                | `ReactElement \| ((props: HTMLProps, state: Calendar.DayButton.State) => ReactElement)` | -                            | Allows you to replace the component’s HTML element&#xA;with a different tag, or compose it with another component.Accepts a `ReactElement` or a function that returns the element to render. |

**DayButton Data Attributes:**

| Attribute          | Type | Description                                                                     |
| :----------------- | :--- | :------------------------------------------------------------------------------ |
| data-selected      | -    | Present when the day is selected.                                               |
| data-disabled      | -    | Present when the day is disabled.                                               |
| data-current       | -    | Present when the day is the current date.                                       |
| data-outside-month | -    | Present when the day is outside the month rendered by the day grid wrapping it. |
| data-unavailable   | -    | Present when the day is unavailable.                                            |

### DecrementMonth

Displays an element to navigate to the previous month in the calendar.
Renders a `<button>` element.

**DecrementMonth Props:**

| Prop         | Type                                                                                         | Default | Description                                                                                                                                                                                  |
| :----------- | :------------------------------------------------------------------------------------------- | :------ | :------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| nativeButton | `boolean`                                                                                    | `true`  | Whether the component renders a native `<button>` element when replacing it&#xA;via the `render` prop.&#xA;Set to `false` if the rendered element is not a button (e.g. `<div>`).            |
| className    | `string \| ((state: Calendar.DecrementMonth.State) => string \| undefined)`                  | -       | CSS class applied to the element, or a function that&#xA;returns a class based on the component’s state.                                                                                     |
| style        | `CSSProperties \| ((state: Calendar.DecrementMonth.State) => CSSProperties \| undefined)`    | -       | -                                                                                                                                                                                            |
| render       | `ReactElement \| ((props: HTMLProps, state: Calendar.DecrementMonth.State) => ReactElement)` | -       | Allows you to replace the component’s HTML element&#xA;with a different tag, or compose it with another component.Accepts a `ReactElement` or a function that returns the element to render. |

**DecrementMonth Data Attributes:**

| Attribute     | Type | Description                          |
| :------------ | :--- | :----------------------------------- |
| data-disabled | -    | Present when the button is disabled. |

### IncrementMonth

Displays an element to navigate to the next month in the calendar.
Renders a `<button>` element.

**IncrementMonth Props:**

| Prop         | Type                                                                                         | Default | Description                                                                                                                                                                                  |
| :----------- | :------------------------------------------------------------------------------------------- | :------ | :------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| nativeButton | `boolean`                                                                                    | `true`  | Whether the component renders a native `<button>` element when replacing it&#xA;via the `render` prop.&#xA;Set to `false` if the rendered element is not a button (e.g. `<div>`).            |
| className    | `string \| ((state: Calendar.IncrementMonth.State) => string \| undefined)`                  | -       | CSS class applied to the element, or a function that&#xA;returns a class based on the component’s state.                                                                                     |
| style        | `CSSProperties \| ((state: Calendar.IncrementMonth.State) => CSSProperties \| undefined)`    | -       | -                                                                                                                                                                                            |
| render       | `ReactElement \| ((props: HTMLProps, state: Calendar.IncrementMonth.State) => ReactElement)` | -       | Allows you to replace the component’s HTML element&#xA;with a different tag, or compose it with another component.Accepts a `ReactElement` or a function that returns the element to render. |

**IncrementMonth Data Attributes:**

| Attribute     | Type | Description                          |
| :------------ | :--- | :----------------------------------- |
| data-disabled | -    | Present when the button is disabled. |

### Viewport

A viewport for displaying calendar month transitions.
This component is only required if you want to animate certain part of a calendar when navigating between months.
The first rendered child element has to handle a ref.
Passes `data-current` to the currently visible content and `data-previous` to the previous content when animating between two.
Doesn't render its own HTML element.

**Viewport Props:**

| Prop     | Type                | Default | Description                                            |
| :------- | :------------------ | :------ | :----------------------------------------------------- |
| children | `React.JSX.Element` | -       | The content to render inside the transition container. |

**Viewport Data Attributes:**

| Attribute                 | Type                             | Description                                                                                                        |
| :------------------------ | :------------------------------- | :----------------------------------------------------------------------------------------------------------------- |
| data-current              | -                                | Applied to the direct child of the viewport when no transitions are present or the new content when it's entering. |
| data-navigation-direction | `'previous' \| 'next' \| 'none'` | Indicates the direction of the navigation (based on the month navigating to).                                      |
| data-previous             | -                                | Applied to the direct child of the viewport that contains the exiting content when transitions are present.        |
| data-starting-style       | -                                | Present when the day grid body is animating in.                                                                    |
| data-ending-style         | -                                | Present when the day grid body is animating out.                                                                   |
