Skip to content

Pickers and overlays

Calendar

A month grid for choosing one day with the keyboard or a click.

When to use it: Use it when seeing the whole month helps, alone or inside a DatePicker.

On this page

Example

July 2026
SMTWTFS

Selected: 2026-07-14

Calendar.tsxtsx
import { useState } from "react";
import {
  Calendar,
  CalendarCell,
  CalendarGrid,
  CalendarHeader,
  CalendarNextButton,
  CalendarPreviousButton,
} from "@comp0/react";
import { ChevronLeftIcon, ChevronRightIcon } from "@heroicons/react/16/solid";

export function Example() {
  const [date, setDate] = useState("2026-07-14");
  return (
    <Calendar
      as="div"
      className="max-w-xs rounded border border-zinc-950/10 bg-white p-3 dark:border-white/10 dark:bg-zinc-900"
      value={date}
      onChange={setDate}
      min="2026-01-01"
      max="2026-12-31"
    >
      <div className="flex items-center justify-between gap-2 pb-2">
        <CalendarPreviousButton className="inline-flex size-8 items-center justify-center rounded text-zinc-700 outline-teal-600 hover:bg-zinc-100 focus-visible:outline-2 disabled:opacity-40 dark:text-zinc-300 dark:outline-teal-400 dark:hover:bg-zinc-800">
          <ChevronLeftIcon className="size-4" aria-hidden="true" />
        </CalendarPreviousButton>
        <CalendarHeader className="text-base font-medium text-zinc-900 sm:text-sm dark:text-zinc-100" />
        <CalendarNextButton className="inline-flex size-8 items-center justify-center rounded text-zinc-700 outline-teal-600 hover:bg-zinc-100 focus-visible:outline-2 disabled:opacity-40 dark:text-zinc-300 dark:outline-teal-400 dark:hover:bg-zinc-800">
          <ChevronRightIcon className="size-4" aria-hidden="true" />
        </CalendarNextButton>
      </div>
      <CalendarGrid className="w-full border-separate border-spacing-0.5 [&_th]:pb-1 [&_th]:text-sm [&_th]:font-normal [&_th]:text-zinc-500 dark:[&_th]:text-zinc-400">
        {(cell) => (
          <CalendarCell
            date={cell.iso}
            outsideMonth={cell.outsideMonth}
            className="p-0 text-center [&>button]:size-9 [&>button]:rounded [&>button]:text-base [&>button]:text-zinc-800 [&>button]:outline-teal-600 [&>button]:hover:bg-zinc-100 [&>button]:focus-visible:outline-2 [&>button]:disabled:opacity-40 [&>button[data-outside-month]]:text-zinc-400 [&>button[data-selected]]:bg-teal-600 [&>button[data-selected]]:text-white [&>button[data-today]]:font-semibold sm:[&>button]:text-sm dark:[&>button]:text-zinc-100 dark:[&>button]:outline-teal-400 dark:[&>button]:hover:bg-zinc-800 dark:[&>button[data-outside-month]]:text-zinc-600 dark:[&>button[data-selected]]:bg-teal-500 dark:[&>button[data-selected]]:text-zinc-950"
          />
        )}
      </CalendarGrid>
      <p className="pt-2 text-base text-zinc-600 sm:text-sm dark:text-zinc-400">Selected: {date}</p>
    </Calendar>
  );
}

Anatomy

Dashed frames are invisible state providers; shaded shapes own real DOM. Numbered pins match the list below.

A wireframe sketch of the assembled component. Each numbered marker matches a part in the list that follows.

  1. Calendar

    Selected-date and visible-month provider. Does not add a DOM element.

  2. CalendarHeader

    Live region announcing the visible month. Owns a DOM element.

  3. CalendarPreviousButtonOptional

    Native button that shows the previous month. Owns a DOM element.

  4. CalendarNextButtonOptional

    Native button that shows the next month. Owns a DOM element.

  5. CalendarGrid

    Month table with localized weekday headers. Owns a DOM element.

  6. CalendarCellOptional

    Day cell wrapping the real day button. Owns a DOM element.

Step by step

  1. 1

    Add the main part

    Wrap CalendarHeader and CalendarGrid in Calendar; the grid renders the month on its own.

  2. 2

    Add the supporting parts

    Add CalendarPreviousButton and CalendarNextButton around the header for month paging.

  3. 3

    Make the behavior clear

    Read the choice from onChange as an ISO date, and fence the range with min/max; arrow keys walk days, PageUp/PageDown walk months.

    Exampletsx
    <Calendar defaultValue="2026-07-14" onChange={setDate}><CalendarHeader /><CalendarGrid /></Calendar>

Keyboard

Moves focus one day back.
Moves focus one day forward.
Moves focus one week back.
Moves focus one week forward.
Home
Moves focus to the start of the week.
End
Moves focus to the end of the week.
PgUp
Shows the previous month.
PgDn
Shows the next month.
PgUp
Shows the previous year.
PgDn
Shows the next year.
Selects the focused day.
Space
Selects the focused day.

Forms and accessibility

Selection does not create a native form value; pair the calendar with a named DateField, or mirror the value into a hidden input.

Accessibility checklist

  • The grid is one tab stop; arrow keys move between days, so keep custom cells as buttons.
  • Month and weekday names come from Intl for the given locale, but the prev/next button labels default to English aria-labels; translate them.
  • The header is a polite live region, so month changes are announced without moving focus.

API reference

Importtsx
import { Calendar, CalendarCell, CalendarGrid, CalendarHeader, CalendarNextButton, CalendarPreviousButton } from "@comp0/react";

Calendar

Context only

Selected-date and visible-month provider.

PropTypeDescription
valuestringControlled date as "YYYY-MM-DD".
defaultValuestringInitial date as "YYYY-MM-DD".
onChange(value: string) => voidReceives the selected ISO date.
min / maxstringInclusive selectable range; days outside it are disabled.
localestringBCP 47 tag for month and weekday names and the week start; defaults to the browser locale.
asElementTypeRenders a wrapper element; there is no DOM without it.

CalendarHeader

DOM element

Live region announcing the visible month.

PropTypeDescription
children(label: string) => ReactNodeCustom content receiving the localized month-and-year label.

CalendarPreviousButton

OptionalDOM element

Native button that shows the previous month.

PropTypeDescription
aria-labelstringDefaults to the English "Previous month"; pass a translation.

CalendarNextButton

OptionalDOM element

Native button that shows the next month.

PropTypeDescription
aria-labelstringDefaults to the English "Next month"; pass a translation.

CalendarGrid

DOM element

Month table with localized weekday headers.

PropTypeDescription
children(cell: MonthMatrixCell) => ReactNodeCustom day cell renderer; defaults to a plain CalendarCell.

CalendarCell

OptionalDOM element

Day cell wrapping the real day button.

PropTypeDescription
datestringThis cell's date as "YYYY-MM-DD".
outsideMonthbooleanMarks a leading or trailing day from a neighboring month.
childrenReactNodeVisible day content; defaults to the day number.

Style hooks

Attributes that appear while a state is true. Target them with Tailwind data variants such as data-open:bg-zinc-100, or with any CSS selector.

[data-selected]

on CalendarCell

The day is selected.
[data-today]

on CalendarCell

The day is today.
[data-outside-month]

on CalendarCell

The day belongs to a neighboring month.
[data-value]

on Calendar (with as)

A date is selected.

Keep exploring