Skip to content

Pickers and overlays

Date Range Picker

Two typed date inputs and a range calendar sharing one date span.

When to use it: Use it for stays, booking windows, and any start–end date pair.

On this page

Example

Check-inCheck-out
Date Range Picker.tsxtsx
import {
  CalendarHeader,
  CalendarNextButton,
  CalendarPreviousButton,
  DateRangePicker,
  DateRangePickerEndField,
  DateRangePickerPopover,
  DateRangePickerStartField,
  DateRangePickerTrigger,
  Label,
  RangeCalendar,
  RangeCalendarGrid,
} from "@comp0/react";
import { CalendarDaysIcon, ChevronLeftIcon, ChevronRightIcon } from "@heroicons/react/20/solid";

export function Example() {
  return (
    <DateRangePicker name="stay" defaultValue={["2026-07-14", "2026-07-18"]}>
      <Label className="mb-2 block text-sm font-medium">Stay dates</Label>
      <div className="flex flex-wrap items-end gap-2">
        <span className="grid w-36 gap-1 text-xs text-zinc-600 dark:text-zinc-400">
          Check-in
          <span className="overflow-hidden rounded border border-zinc-950/15 bg-white outline-teal-600 focus-within:outline-2 dark:border-white/15 dark:bg-zinc-900 dark:outline-teal-400">
            <DateRangePickerStartField
              aria-label="Check-in"
              className="w-[calc(100%+2.5rem)] border-0 bg-transparent px-2 py-2 text-sm text-zinc-950 outline-none dark:text-zinc-50 [&::-webkit-calendar-picker-indicator]:hidden"
            />
          </span>
        </span>
        <span className="grid w-36 gap-1 text-xs text-zinc-600 dark:text-zinc-400">
          Check-out
          <span className="overflow-hidden rounded border border-zinc-950/15 bg-white outline-teal-600 focus-within:outline-2 dark:border-white/15 dark:bg-zinc-900 dark:outline-teal-400">
            <DateRangePickerEndField
              aria-label="Check-out"
              className="w-[calc(100%+2.5rem)] border-0 bg-transparent px-2 py-2 text-sm text-zinc-950 outline-none dark:text-zinc-50 [&::-webkit-calendar-picker-indicator]:hidden"
            />
          </span>
        </span>
        <DateRangePickerTrigger className="inline-flex h-9.5 items-center justify-center rounded border border-zinc-950/15 px-2.5 text-sm outline-teal-600 focus-visible:outline-2 dark:border-white/15 dark:outline-teal-400">
          <CalendarDaysIcon className="size-4" aria-hidden="true" />
        </DateRangePickerTrigger>
      </div>
      <DateRangePickerPopover
        placement="bottom end"
        offset={4}
        className="rounded-lg border border-zinc-950/10 bg-white p-3 shadow-xl dark:border-white/10 dark:bg-zinc-900"
      >
        <RangeCalendar>
          <div className="mb-2 flex items-center justify-between">
            <CalendarPreviousButton className="rounded p-2 hover:bg-zinc-100 dark:hover:bg-zinc-800">
              <ChevronLeftIcon className="size-4" aria-hidden="true" />
            </CalendarPreviousButton>
            <CalendarHeader className="text-sm font-semibold" />
            <CalendarNextButton className="rounded p-2 hover:bg-zinc-100 dark:hover:bg-zinc-800">
              <ChevronRightIcon className="size-4" aria-hidden="true" />
            </CalendarNextButton>
          </div>
          <RangeCalendarGrid className="border-separate border-spacing-0 text-sm [&_button]:size-9 [&_button]:outline-teal-600 [&_button:focus-visible]:rounded [&_button:focus-visible]:outline-2 [&_button:hover]:rounded [&_button:hover]:bg-zinc-100 [&_td]:p-0 [&_td[data-in-range]]:bg-teal-50 [&_td[data-range-end]]:bg-teal-600 [&_td[data-range-end]_button]:text-white [&_td[data-range-preview]]:bg-teal-100 [&_td[data-range-start]]:bg-teal-600 [&_td[data-range-start]_button]:text-white dark:[&_button]:outline-teal-400 dark:[&_button:hover]:bg-zinc-800 dark:[&_td[data-in-range]]:bg-teal-950 dark:[&_td[data-range-preview]]:bg-teal-900" />
        </RangeCalendar>
      </DateRangePickerPopover>
    </DateRangePicker>
  );
}

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

    Range value, field wiring, form serialization, and popover-state provider. Does not add a DOM element.

  2. Label

    Visible name wired to the start field. Owns a DOM element.

  3. DateRangePickerStartField

    Native start-date input; the default label comes from Label. Owns a DOM element.

  4. DateRangePickerEndField

    Native end-date input; defaults to the English label "End date". Owns a DOM element.

  5. DateRangePickerTrigger

    Button opening the range calendar; defaults to "Choose dates". Owns a DOM element.

  6. DateRangePickerPopover

    Floating calendar dialog; defaults to the English label Calendar. Owns a DOM element.

  7. RangeCalendar

    Range month grid that adopts the picker value and completes the range. Owns a DOM element.

Step by step

  1. 1

    Add the main part

    Put a Label, both date fields, and a trigger inside DateRangePicker; hide both browser-owned indicators so only the custom trigger is visible.

  2. 2

    Add the supporting parts

    Place a RangeCalendar inside DateRangePickerPopover so it adopts the same range.

  3. 3

    Make the behavior clear

    Pass name to submit `${name}-start` and `${name}-end`; choosing a second day closes the popover and returns focus to the trigger.

    Exampletsx
    <DateRangePicker name="stay">
      <Label>Stay dates</Label>
      <DateRangePickerStartField />
      <DateRangePickerEndField />
      <DateRangePickerTrigger />
      <DateRangePickerPopover>
        <RangeCalendar>
          <RangeCalendarGrid />
        </RangeCalendar>
      </DateRangePickerPopover>
    </DateRangePicker>;

Keyboard

Space
Opens the calendar from the trigger or selects the focused day.
Esc
Closes the calendar and returns focus to the trigger.
Moves through days in the open calendar.

Forms and accessibility

DateRangePicker submits two native date inputs named `${name}-start` and `${name}-end` and restores uncontrolled values on form reset.

Accessibility checklist

  • Keep both date fields editable; the calendar must not be the only way to enter a range.
  • When adding DateRangePickerTrigger, you must hide both browser-owned indicators so only one picker button is visible.
  • The trigger and popover default to English labels ("Choose dates" and "Calendar"); translate them for localized apps.
  • Opening focuses a date in the calendar and completing a range returns focus to the trigger; Escape closes without changing the values.

API reference

Importtsx
import { CalendarHeader, CalendarNextButton, CalendarPreviousButton, DateRangePicker, DateRangePickerEndField, DateRangePickerPopover, DateRangePickerStartField, DateRangePickerTrigger, Label, RangeCalendar, RangeCalendarGrid } from "@comp0/react";

DateRangePicker

Context only

Range value, field wiring, form serialization, and popover-state provider.

PropTypeDescription
value[string, string]Controlled [start, end] dates as "YYYY-MM-DD".
defaultValue[string, string]Initial [start, end] dates as "YYYY-MM-DD".
onChange(value: DateRange) => voidReceives the next start and end pair.
openbooleanControlled calendar popover state.
defaultOpenbooleanInitial calendar popover state.
onToggle(open: boolean) => voidReceives the next popover state.
namestringSubmits `${name}-start` and `${name}-end` date inputs.
formstringAssociates both submitted dates with a form by id.
disabledbooleanField-wide states for both dates.
invalidbooleanField-wide states for both dates.
requiredbooleanField-wide states for both dates.

Label

DOM element

Visible name wired to the start field.

DateRangePickerStartField

DOM element

Native start-date input; the default label comes from Label.

DateRangePickerEndField

DOM element

Native end-date input; defaults to the English label "End date".

DateRangePickerTrigger

DOM element

Button opening the range calendar; defaults to "Choose dates".

DateRangePickerPopover

DOM element

Floating calendar dialog; defaults to the English label Calendar.

PropTypeDescription
placementPopoverPlacementSide to open on, with automatic flipping.
offsetnumberPixel gap between trigger and popover.

RangeCalendar

DOM element

Range month grid that adopts the picker value and completes the range.

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.

DateRangePicker

Style hookMeaning
[data-complete]Both start and end dates have values.
[data-start-value]The selected ISO dates.
[data-end-value]The selected ISO dates.
[data-disabled]The shared field state.
[data-invalid]The shared field state.
[data-required]The shared field state.

DateRangePickerTrigger

Style hookMeaning
[data-open]The calendar is open.

DateRangePickerPopover

Style hookMeaning
[data-open]The calendar is open.

Keep exploring