component — date & time
Date Range Picker
Two segmented date inputs (start → end) paired with a range-calendar overlay, built on react-aria-components and styled with the quebi design system. The trigger uses the quebi input chrome with a calendar-icon button; a popover (or modal on mobile) holds the range calendar. One shouldForceLeadingZeros pads the day and month of both inputs to two digits.
- form
- input
- date
- daterange
- range
- calendar
- popover
- interactive
- leading-zeros
07 examples
Default
A labelled date range picker with a range-calendar overlay.
With value
Pre-filled via @internationalized/date.
Two months
Show two months at once via visibleDuration.
Two digits
shouldForceLeadingZeros pads the day and month segments of both inputs to two digits (30.06.2026 rather than the 30.6.2026 de-DE asks for).
Invalid
Validation state surfaces a red border and an error message.
Disabled
Controlled
source
date-range-picker.tsxCopy this into your project. Resolve its dependencies from the registryDependencies in the component's API entry.
"use client"
import type { DateDuration } from "@internationalized/date"
import { CalendarDays } from "lucide-react"
import {
Button,
composeRenderProps,
DateRangePicker as DateRangePickerPrimitive,
type DateRangePickerProps as DateRangePickerPrimitiveProps,
type DateValue,
Group,
type GroupProps,
type PopoverProps,
} from "react-aria-components"
import { DateInput } from "@/components/date-field"
import { DatePickerOverlay } from "@/components/date-picker"
import { cn } from "@/lib/utils"
import { fieldStyles } from "@/components/field"
/**
* Date Range Picker — quebi design system
*
* Two segmented date inputs (start → end) paired with a range-calendar overlay.
* The trigger is drawn with `Input`'s box, with a calendar-icon button on the right; clicking it
* opens a Popover (or Modal on mobile) holding the RangeCalendar. Composes
* @/components/{date-picker,date-field,field}. The Conform date-range-picker
* variant depends on it.
*
* Both halves are `DateInput`s, so both format the way `DateField` does:
* locale-derived, `30.6.2026` under `de-DE`. `shouldForceLeadingZeros` pads the
* day and month of start *and* end to two digits (`30.06.2026`) — it is one
* prop on the range, not one per input.
*/
export interface DateRangePickerProps<T extends DateValue>
extends DateRangePickerPrimitiveProps<T> {
visibleDuration?: DateDuration
pageBehavior?: "visible" | "single"
popover?: Omit<PopoverProps, "children">
}
export function DateRangePicker<T extends DateValue>({
className,
popover,
children,
visibleDuration = { months: 1 },
pageBehavior = "visible",
...props
}: DateRangePickerProps<T>) {
return (
<DateRangePickerPrimitive
data-slot="control"
className={composeRenderProps(className, (resolved) =>
cn("group", fieldStyles, resolved),
)}
{...props}
>
{(values) => (
<>
{typeof children === "function" ? children(values) : children}
<DatePickerOverlay
{...popover}
range
visibleDuration={visibleDuration}
pageBehavior={pageBehavior}
/>
</>
)}
</DateRangePickerPrimitive>
)
}
/**
* DateRangePickerTrigger — quebi design system
*
* A start DateInput, a separator dash, and an end DateInput on the left, plus a
* calendar-icon button on the right, read as one control: one box around
* all of it, owned by the wrapper. The inner DateInputs are rendered `bare`
* so they draw no edge of their own.
*/
export function DateRangePickerTrigger({ className, ...props }: GroupProps) {
return (
<Group
data-slot="control"
className={composeRenderProps(className, (resolved) =>
cn(
// `Input`'s box, around the segments and the calendar button alike;
// focus anywhere inside, or the open calendar, turns its edge.
"group/drpt flex w-full items-stretch overflow-hidden",
"quebi-field",
"transition-[border-color,box-shadow] duration-150",
"focus-within:shadow-(--q-field-focus) group-open:shadow-(--q-field-focus)",
"data-invalid:[--q-field-edge:var(--q-danger)] data-invalid:focus-within:shadow-(--q-field-focus-danger)",
resolved,
),
)}
{...props}
>
<div className="flex flex-1 items-center">
<DateInput slot="start" bare className="w-fit pe-2" />
<span aria-hidden="true" className="block h-px w-2 shrink-0 bg-quebi-fg-subtle" />
<DateInput slot="end" bare className="w-fit px-2" />
</div>
<Button
data-slot="date-picker-trigger"
className={cn(
"inline-flex cursor-pointer items-center bg-transparent ps-3 pe-(--q-field-px) text-quebi-fg-subtle",
"transition-colors duration-150 hover:text-quebi-fg",
"outline-none focus-visible:text-quebi-fg focus-visible:ring-2 focus-visible:ring-quebi-focus focus-visible:ring-inset",
)}
>
<CalendarDays data-slot="icon" className="size-4" />
</Button>
</Group>
)
}