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.
Example
| S | M | T | W | T | F | S |
|---|---|---|---|---|---|---|
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.
DateRangePicker
Range value, field wiring, form serialization, and popover-state provider. Does not add a DOM element.
Label
Visible name wired to the start field. Owns a DOM element.
DateRangePickerStartField
Native start-date input; the default label comes from Label. Owns a DOM element.
DateRangePickerEndField
Native end-date input; defaults to the English label "End date". Owns a DOM element.
DateRangePickerTrigger
Button opening the range calendar; defaults to "Choose dates". Owns a DOM element.
DateRangePickerPopover
Floating calendar dialog; defaults to the English label Calendar. Owns a DOM element.
RangeCalendar
Range month grid that adopts the picker value and completes the range. Owns a DOM element.
Step by step
- 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
Add the supporting parts
Place a RangeCalendar inside DateRangePickerPopover so it adopts the same range.
- 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
import { CalendarHeader, CalendarNextButton, CalendarPreviousButton, DateRangePicker, DateRangePickerEndField, DateRangePickerPopover, DateRangePickerStartField, DateRangePickerTrigger, Label, RangeCalendar, RangeCalendarGrid } from "@comp0/react";DateRangePicker
Context onlyRange value, field wiring, form serialization, and popover-state provider.
| Prop | Type | Description |
|---|---|---|
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) => void | Receives the next start and end pair. |
open | boolean | Controlled calendar popover state. |
defaultOpen | boolean | Initial calendar popover state. |
onToggle | (open: boolean) => void | Receives the next popover state. |
name | string | Submits `${name}-start` and `${name}-end` date inputs. |
form | string | Associates both submitted dates with a form by id. |
disabled | boolean | Field-wide states for both dates. |
invalid | boolean | Field-wide states for both dates. |
required | boolean | Field-wide states for both dates. |
Label
DOM elementVisible name wired to the start field.
DateRangePickerStartField
DOM elementNative start-date input; the default label comes from Label.
DateRangePickerEndField
DOM elementNative end-date input; defaults to the English label "End date".
DateRangePickerTrigger
DOM elementButton opening the range calendar; defaults to "Choose dates".
DateRangePickerPopover
DOM elementFloating calendar dialog; defaults to the English label Calendar.
| Prop | Type | Description |
|---|---|---|
placement | PopoverPlacement | Side to open on, with automatic flipping. |
offset | number | Pixel gap between trigger and popover. |
RangeCalendar
DOM elementRange 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 hook | Meaning |
|---|---|
[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 hook | Meaning |
|---|---|
[data-open] | The calendar is open. |
DateRangePickerPopover
| Style hook | Meaning |
|---|---|
[data-open] | The calendar is open. |