DateRangeField

A React date range picker - two taps set the start and end. For booking forms and analytics date filters.

DateRangeField preview

New in 0.11

Install DateRange

1. New project? One command installs the package and wires everything - see Installation:

bash
pnpm dlx zyncat-ui init

2. Import the component - it loads its own stylesheet:

tsx
import { DateRange } from '@zyncat/ui/date-range';

Usage

tsx
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

PropTypeDefaultDescription
valueDateRange | null—Controlled value - both endpoints, or null when empty.
defaultValueDateRange | nullnullUncontrolled initial range. Use instead of `value`.
onChange(value: DateRange) => void—Fires only on a COMPLETE range (a lone anchor never commits).
placeholderstring'Pick a date range'Trigger text shown when no range is picked.
timezonestring—IANA timezone (e.g. 'Europe/Riga') - display context, shown in the footer.
minstring—Earliest pickable date, 'YYYY-MM-DD', inclusive.
maxstring—Latest pickable date, 'YYYY-MM-DD', inclusive.
labelstring—Field label rendered above the trigger.
requiredbooleanfalseAsterisk on the label.
invalidbooleanfalseDanger border + message color (.zc-fld.zc-is-error).
messagestring—Helper / error text under the field.
disabledbooleanfalseDisable the field.
size'sm' | 'md' | 'lg''md'Control height, type and padding - the same scale as `TextField`, so a form row can hold one size throughout.
classNamestring—Extra class on the field shell root.
...htmlAttributesHTMLAttributes<HTMLDivElement> & DataAttributes—Standard <div> attributes (style, data-*, aria-*, ...) forwarded to the field shell root.
activateOnActivateOn'pointerdown'Whether the trigger, the day cells and the panel controls fire on `pointerdown` (snappier) or wait for `click`. (Not used by TimeField, which is inline.)
animationDisableableAnimation—Popover open/close timing - motion tokens only, or `null` to disable. (Not used by TimeField, which is inline.)

DateRange

PropTypeDefaultDescription
start*string—Range start, inclusive - `'YYYY-MM-DD'`.
end*string—Range end, inclusive - `'YYYY-MM-DD'`. Never before `start`.

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.

Set in Geist & Newsreader — animated by the house engineZyncat UI · Rev 0.11 · MIT · Built by Tabsir Ahammed · Source on GitHub