Skip to content

Pickers and overlays

Date Picker

A typed date input with a calendar popover sharing one value.

When to use it: Use it when people know some dates by heart and browse for others.

On this page

Example

Date Picker.tsxtsx
import { Label } from "@comp0/react";
import {
  Calendar,
  CalendarGrid,
  CalendarHeader,
  CalendarNextButton,
  CalendarPreviousButton,
  DateField,
  DatePicker,
  DatePickerPopover,
  DatePickerTrigger,
} from "@comp0/react";
import { CalendarIcon, ChevronLeftIcon, ChevronRightIcon } from "@heroicons/react/16/solid";

export function Example() {
  return (
    <DatePicker as="div" className="flex max-w-xs flex-col gap-1.5" defaultValue="2026-07-14">
      <Label className="text-base font-medium text-zinc-900 sm:text-sm dark:text-zinc-100">
        Reservation date
      </Label>
      <div className="flex gap-1.5">
        <div className="w-full overflow-hidden rounded border border-zinc-950/10 bg-white outline-teal-600 focus-within:outline-2 dark:border-white/10 dark:bg-zinc-900 dark:outline-teal-400">
          <DateField className="w-[calc(100%+2.5rem)] border-0 bg-transparent px-3 py-2.5 text-base text-zinc-950 outline-none sm:py-2 sm:text-sm dark:text-zinc-50 [&::-webkit-calendar-picker-indicator]:hidden" />
        </div>
        <DatePickerTrigger className="rounded border border-zinc-950/10 bg-white px-3 text-zinc-700 outline-teal-600 focus-visible:outline-2 data-open:bg-zinc-100 dark:border-white/10 dark:bg-zinc-900 dark:text-zinc-300 dark:outline-teal-400 dark:data-open:bg-zinc-800">
          <CalendarIcon className="size-4" aria-hidden="true" />
        </DatePickerTrigger>
      </div>
      <DatePickerPopover
        placement="bottom end"
        offset={4}
        className="rounded border-0 bg-white p-3 shadow-lg ring-1 ring-zinc-950/10 dark:bg-zinc-900 dark:shadow-none dark:ring-white/10"
      >
        <Calendar>
          <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 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 dark:text-zinc-300 dark:outline-teal-400 dark:hover:bg-zinc-800">
              <ChevronRightIcon className="size-4" aria-hidden="true" />
            </CalendarNextButton>
          </div>
          <CalendarGrid className="border-separate border-spacing-0.5 [&_td]:p-0 [&_td]:text-center [&_td_button]:size-9 [&_td_button]:rounded [&_td_button]:text-base [&_td_button]:text-zinc-800 [&_td_button]:outline-teal-600 [&_td_button]:hover:bg-zinc-100 [&_td_button]:focus-visible:outline-2 [&_td_button[data-outside-month]]:text-zinc-400 [&_td_button[data-selected]]:bg-teal-600 [&_td_button[data-selected]]:text-white sm:[&_td_button]:text-sm dark:[&_td_button]:text-zinc-100 dark:[&_td_button]:outline-teal-400 dark:[&_td_button]:hover:bg-zinc-800 dark:[&_td_button[data-outside-month]]:text-zinc-600 dark:[&_td_button[data-selected]]:bg-teal-500 dark:[&_td_button[data-selected]]:text-zinc-950 [&_th]:pb-1 [&_th]:text-sm [&_th]:font-normal [&_th]:text-zinc-500 dark:[&_th]:text-zinc-400" />
        </Calendar>
      </DatePickerPopover>
    </DatePicker>
  );
}

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

    Shared date value, open state, and field-context provider. Does not add a DOM element.

  2. Label

    Visible name connected to the DateField. Owns a DOM element.

  3. DateFieldOptional

    Native date input that reads and writes the picker's value. Owns a DOM element.

  4. DatePickerTrigger

    Native button that opens the calendar. Owns a DOM element.

  5. DatePickerPopover

    The calendar dialog surface. Owns a DOM element.

  6. Calendar

    Month grid that adopts the picker's value and closes it on selection. Owns a DOM element.

Step by step

  1. 1

    Add the main part

    Put a Label, a DateField, and a DatePickerTrigger inside DatePicker.

  2. 2

    Add the supporting parts

    Put a Calendar with CalendarHeader and CalendarGrid inside DatePickerPopover; it adopts the shared value automatically.

  3. 3

    Make the behavior clear

    Read the value from onChange as an ISO date; picking a day closes the popover and refills the DateField, and typing updates the calendar.

    Exampletsx
    <DatePicker defaultValue="2026-07-14"><Label>Reservation</Label><DateField /><DatePickerTrigger /><DatePickerPopover><Calendar><CalendarHeader /><CalendarGrid /></Calendar></DatePickerPopover></DatePicker>

Keyboard

Opens the calendar from the trigger; selects the focused day inside.
Space
Opens the calendar from the trigger; selects the focused day inside.
Esc
Closes the calendar and returns focus to the trigger.
Move between days in the open calendar.

Forms and accessibility

The nested DateField owns form participation: give it a name and the shared value submits as YYYY-MM-DD with native validation.

Accessibility checklist

  • Keep the DateField: typing a date must stay possible without opening the calendar.
  • The trigger and surface default to English aria-labels ("Choose date", "Calendar"); translate them for localized apps.
  • Opening moves focus to the focused day and closing returns it to the trigger; Escape always closes without changing the value.
  • When adding a separate trigger, keep only one picker button visible; the example clips the browser-owned indicator without removing the native DateField.

API reference

Importtsx
import { Calendar, CalendarGrid, CalendarHeader, DateField, DatePicker, DatePickerPopover, DatePickerTrigger, Label } from "@comp0/react";

DatePicker

Context only

Shared date value, open state, and field-context provider.

PropTypeDescription
valuestringControlled date as "YYYY-MM-DD".
defaultValuestringInitial date as "YYYY-MM-DD".
onChange(value: string) => voidReceives the selected ISO date.
open / defaultOpenbooleanControlled or initial open state of the calendar.
onToggle(open: boolean) => voidReceives the next open state.
namestringSubmission name for the selected ISO date.
formstringAssociates the date value with a form by id.
disabled / invalid / requiredbooleanField-wide states.
asElementTypeRenders a wrapper element; there is no DOM without it.

Label

DOM element

Visible name connected to the DateField.

DateField

OptionalDOM element

Native date input that reads and writes the picker's value.

PropTypeDescription
namestringSubmission name; the value submits as YYYY-MM-DD.

DatePickerTrigger

DOM element

Native button that opens the calendar.

PropTypeDescription
aria-labelstringDefaults to the English "Choose date"; pass a translation.
disabledbooleanDisables opening.

DatePickerPopover

DOM element

The calendar dialog surface.

PropTypeDescription
aria-labelstringDefaults to the English "Calendar"; pass a translation.
placementPopoverPlacementTrigger side to open on, such as "bottom end"; flips when there is no room.
offsetnumberPixel gap between the trigger and the surface.

Calendar

DOM element

Month grid that adopts the picker's value and closes it on selection.

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-open]

on DatePickerTrigger, DatePickerPopover

The calendar is open.
:popover-open

on DatePickerPopover

Native pseudo-class equivalent.
[data-value]

on DatePicker (with as)

A date is selected.

Keep exploring