Skip to content
weftGitHub

DateRangeField

Pick a start and end date, with presets like Last 30 days and an optional comparison.

SourcePackage @weft-js/warp

Preview

tsx
import { DateRangeField } from '@weft-js/warp'import type { IsoDate } from '@weft-js/warp'export function Presets() {  return (    <DateRangeField label="Period" today={TODAY} defaultValue={{ start: '2026-10-01', end: '2026-10-31' }} />  )}const TODAY: IsoDate = '2026-11-02'

Installation

npm install @weft-js/warp

Usage

tsx
import { DateRangeField } from '@weft-js/warp'<DateRangeField label="Period" today={TODAY} defaultValue={{ start: '2026-10-01', end: '2026-10-31' }} />

Your own presets

Each preset is a function of today.

tsx
import { DateRangeField } from '@weft-js/warp'import type { DateRange, IsoDate } from '@weft-js/warp'export function Custom() {  return (    <DateRangeField label="Report on" today={TODAY} presets={REPORTING} defaultValue={Q3} />  )}const TODAY: IsoDate = '2026-11-02'const REPORTING = [  { id: 'sprint', label: 'This sprint', range: (): DateRange => ({ start: '2026-10-26', end: '2026-11-06' }) },  { id: 'q3', label: 'Q3', range: (): DateRange => ({ start: '2026-07-01', end: '2026-09-30' }) },  { id: 'ytd', label: 'Year to date', range: (t: IsoDate): DateRange => ({ start: '2026-01-01', end: t }) },]const Q3: DateRange = { start: '2026-07-01', end: '2026-09-30' }

A longest span

maxDays stops the range growing past a limit.

tsx
import { DateRangeField } from '@weft-js/warp'import type { IsoDate } from '@weft-js/warp'export function Limit() {  return (    <DateRangeField label="Log export" hint="Up to 14 days at a time" today={TODAY} presets={false} months={1} maxDays={14} max={TODAY} defaultValue={{ start: '2026-10-20', end: '2026-10-27' }} />  )}const TODAY: IsoDate = '2026-11-02'

Compare

compare shades the earlier period in the calendar.

tsx
import { state } from '@weft-js/core'import { DateRangeField, Stack, Text, compareRange } from '@weft-js/warp'import type { DateRange, IsoDate } from '@weft-js/warp'export function Compare() {  const range = state<DateRange>({ start: '2026-10-01', end: '2026-10-31' })  const before = () => compareRange(range.value, 'previous-period')  return (    <Stack gap="related">      <DateRangeField label="Period" today={TODAY} value={range} compare="previous-period" />      <Text subtle role="caption">Compared with {before()?.start} to {before()?.end}</Text>    </Stack>  )}const TODAY: IsoDate = '2026-11-02'

Filter a list

Change the range, and the list follows.

tsx
import { state } from '@weft-js/core'import { DateRangeField, Row, Stack, Text } from '@weft-js/warp'import type { DateRange, IsoDate } from '@weft-js/warp'export function Filter() {  const range = state<DateRange>({ start: '2026-10-31', end: '2026-10-31' })  const shown = () => DEPLOYS.filter(d => d.at.slice(0, 10) >= (range.value.start ?? '') && d.at.slice(0, 10) <= (range.value.end ?? '9999'))  return (    <Stack gap="group">      <DateRangeField label="Deploys between" today={TODAY} value={range} months={1} max={TODAY} />      <Text role="label">{shown().length} deploys</Text>      <Stack gap="related" divided>        {shown().map(d => (          <Row key={d.id} gap="related" justify="between">            <Text>{d.project}</Text>            <Text subtle role="caption">{d.at.slice(5).replace('T', ' at ')}</Text>          </Row>        ))}      </Stack>    </Stack>  )}const DEPLOYS = [  { id: 'd-4821', project: 'storefront', branch: 'main', commit: '9f3ac21', message: 'Cache product images at the edge', author: 'Priya Raman', status: 'Live', minutes: 2, at: '2026-10-31T16:42' },  { id: 'd-4820', project: 'search-api', branch: 'ranking-v2', commit: '1be07d4', message: 'Rank exact matches first', author: 'Lena Fischer', status: 'Building', minutes: 6, at: '2026-10-31T16:10' },  { id: 'd-4819', project: 'mobile-bff', branch: 'main', commit: 'c44e190', message: 'Retry timed out upstream calls', author: 'Sam Okafor', status: 'Failed', minutes: 4, at: '2026-10-31T15:27' },  { id: 'd-4818', project: 'checkout', branch: 'main', commit: '70d2fa8', message: 'Add Apple Pay to the last step', author: 'Tomás Oliveira', status: 'Live', minutes: 3, at: '2026-10-31T14:03' },  { id: 'd-4817', project: 'edge-cache', branch: 'main', commit: 'e81b6c2', message: 'Lower the stale window to 30s', author: 'Jun Tanaka', status: 'Live', minutes: 1, at: '2026-10-31T11:55' },  { id: 'd-4816', project: 'billing', branch: 'main', commit: '2ad9e03', message: 'Prorate seats on plan change', author: 'Mara Lindqvist', status: 'Live', minutes: 3, at: '2026-10-29T17:20' },]const TODAY: IsoDate = '2026-11-02'

When to use

Use it for

  • Filtering a report or a list by dates
  • Booking a span of days

Reach for something else

  • PeriodPicker, when the range is a whole month, quarter or year

Accessibility

Arrowsinthecalendar
Move by day or week, previewing the range
Enter
Sets the start, then the end
Escape
Closes the calendar

API Reference

PropTypeDefaultDescription
labelstring—The accessible name, drawn as the floating border label (or above, classic). Required.
hintunknown—One line of help under the control.
errorunknown—Shown instead of the hint; marks the field invalid.
iconIconRef—Leading icon: it sweeps with the border.
prefixunknown—Text or an element inside the field before the value, such as $.
suffixunknown—Text or an element inside the field after the value, such as kg.
variant"floating" | "classic"—floating (default, or the app's warp({ fields })) · classic: the label above.
appearance"line"—line: only a bottom line under the value, no box or fill. The boxed field otherwise.
size"sm" | "md" | "lg"'md'Control size. md by default.
clearablebooleanfalseA clear button (shown once there is a value).
counternumber—Maximum length, shown as a counter.
idstring—The element id. One is made for you if you leave it out.
disabledbooleanfalseTurns it off, and takes it out of the tab order.
readonlybooleanfalseShows the value and lets it be selected and copied, but not changed.
requiredbooleanfalseThe form will not submit without it.
namestring—The name it posts under in a form.
localestring—The language and region used to format values.
todaystring—Pins "today" (server render, tests); the browser's clock otherwise.
presentationPresentation—auto opens a popover, or a sheet when the viewport is under 600px wide; popover and sheet force one.
valueDateRange | State<DateRange>—The current value, when you own the state.
defaultValueDateRange—The starting value, when the component owns the state.
openboolean | State<boolean>—A state box that holds whether it is open.
defaultOpenbooleanfalseStarts open, when the component keeps its own open state.
presetsfalse | RangePreset[] | "default"'default'A list of your own, "default", or false for none.
presetstring | null—The id of the chosen preset, such as "Last 7 days". Null when the dates were picked by hand.
min`${number}-${number}-${number}`—The lowest allowed value.
max`${number}-${number}-${number}`—The highest allowed value.
maxDaysnumber—The longest range allowed.
isDateUnavailable((d: `${number}-${number}-${number}`) => boolean)—Return true for a day that can't start or end the range: it shows struck through.
months2 | 1 | "auto"'auto'Months shown side by side. auto follows the width.
compareRangeCompare | null—previous-period, previous-year or a range of your own.
EventTypeDefaultDescription
onInput((e: InputEvent & { currentTarget: HTMLInputElement & HTMLTextAreaElemen…—Called on each keystroke.
onValueChange((v: DateRange, reason: ChangeReason) => void)—Called with each new value, and whether a pointer, a key or code made the change.
onOpenChange((o: boolean) => void)—Called with true when it opens and false when it closes.
onPresetChange((id: string | null) => void)—Called with the preset's id when one is picked, and null when the dates are changed by hand.

Parts

Every element carries a stable name, so you can style it from your own CSS: [data-scope="range-panel"] [data-part="range-sep"]

range-sepfoot

RelatedDate fieldPeriod pickerInline calendar