A React date range picker - two taps set the start and end. For booking forms and analytics date filters.
DateRangeField preview
Install DateRange
1. New project? One command installs the package and wires everything - see Installation:
pnpm dlx zyncat-ui init2. Import the component - it loads its own stylesheet:
import { DateRange } from '@zyncat/ui/date-range';Usage
import { DateRangeField } from '@zyncat/ui/date-range-field'; <DateRangeField label="Campaign duration" value={range} onChange={setRange} />Compose DateRangeField in your application. No Tailwind or external styling library is required; every value resolves from Zyncat UI's token vocabulary.
DateRangeField props
DateRangeField
DateRange
Frequently asked questions
Import it per subpath and give it a label and a value: import { DateRangeField } from '@zyncat/ui/date-range-field', then <DateRangeField label="Reporting period" value={range} onChange={setRange} />. It is a client component, so it drops straight into the Next.js App Router - link @zyncat/ui/styles.css once at the root, and pass defaultValue instead of value to run it uncontrolled. On viewports 640px and narrower the panel switches from a popover to a bottom sheet on its own.
A range needs two endpoints: the first day you click becomes a provisional anchor, hovering or arrowing to another day previews the band before anything commits, and the second click auto-orders the pair into { start, end } regardless of which end you clicked first, then fires onChange once. value and defaultValue take that same DateRange shape, both 'YYYY-MM-DD', or null when empty - a lone anchor never commits, so a half-made pick can't leak into your state.
Pass min, max or both as 'YYYY-MM-DD', each inclusive: <DateRangeField label="Stay dates" min="2026-09-02" />. Days outside the window render disabled so neither endpoint can land there, and a month's arrow disables once that whole adjacent month falls outside the bounds. A separate timezone prop takes an IANA name like 'Europe/Riga' and only labels the footer with its GMT offset - it never shifts the committed start or end.
Yes - eight presets sit beside the calendar: Today, Yesterday, Last 7 days, Last 30 days, This month, Last month, Last 90 days and Year to date, the same vocabulary a booking form's check-in/check-out step or an analytics dashboard's reporting-date filter already uses. Clicking one commits a complete range immediately, with no anchor step.
No - @zyncat/ui has zero runtime dependencies and React 19 as its only peer. The two-month grid, the day-count readout and the min/max comparison all run on plain string ordering and the built-in Date; Intl.DateTimeFormat appears once, inside the optional timezone label. Nothing but the component and its CSS lands in your bundle.
Yes - the grid is a real role="grid" with roving focus, arrow keys move a day or a week, PageUp and PageDown change month, Enter or Space sets the anchor then commits the pair, and Escape cancels a half-picked anchor without closing the panel. Every cell carries its full date as an aria-label and the panel is a role="dialog" named by your label; under prefers-reduced-motion the sliding month animation is skipped and the pill that travels between the start and end caps settles instantly.