Skip to content
weftGitHub

DateTimeField

One field for a date and a time, with the calendar beside a list of times.

SourcePackage @weft-js/warp

Preview

tsx
import { state } from '@weft-js/core'import { DateTimeField, Stack, Text } from '@weft-js/warp'import type { IsoDate, IsoDateTime } from '@weft-js/warp'export function Window() {  const at = state<IsoDateTime | null>('2026-11-07T22:00')  return (    <Stack gap="related">      <DateTimeField label="Maintenance starts" today={TODAY} value={at} min="2026-11-02" />      <Text subtle role="caption">{at.value ? `Sent to the API as ${at.value}` : 'Pick a date and a time'}</Text>    </Stack>  )}const min = 60_000const TODAY: IsoDate = '2026-11-02'

Installation

npm install @weft-js/warp

Usage

tsx
import { DateTimeField } from '@weft-js/warp'

Quarter hours

step sets the gap between listed times, in minutes.

tsx
import { DateTimeField } from '@weft-js/warp'import type { IsoDate } from '@weft-js/warp'export function Step() {  return (    <DateTimeField label="Reminder" hint="On the quarter hour" today={TODAY} defaultValue="2026-11-05T09:15" step={15} firstDayOfWeek={1} />  )}const TODAY: IsoDate = '2026-11-02'

Limits

min, max and isDateUnavailable turn days off in the calendar, here weekends and the holiday freeze.

tsx
import { DateTimeField } from '@weft-js/warp'import type { IsoDate } from '@weft-js/warp'export function Limits() {  return (    <DateTimeField label="Deploy at" hint="Weekdays only, before the freeze on Dec 19." today={TODAY} defaultValue="2026-11-04T10:00" min={TODAY} max="2026-12-19" isDateUnavailable={isWeekend} firstDayOfWeek={1} />  )}const min = 60_000const TODAY: IsoDate = '2026-11-02'const isWeekend = (d: IsoDate) => [0, 6].includes(new Date(`${d}T12:00Z`).getUTCDay())

Hint, error, read-only, disabled

The same hint, error, readonly and disabled as every field.

tsx
import { DateTimeField, Stack, Text, Grid } from '@weft-js/warp'import type { IsoDate } from '@weft-js/warp'export function States() {  return (    <Grid cols={2} gap="group">      <Cell name="Empty"><DateTimeField label="Starts" today={TODAY} /></Cell>      <Cell name="Hint"><DateTimeField label="Starts" today={TODAY} hint="In your time zone." /></Cell>      <Cell name="Invalid"><DateTimeField label="Starts" today={TODAY} defaultValue="2026-10-30T09:00" error="Pick a time in the future." /></Cell>      <Cell name="Read-only"><DateTimeField label="Starts" today={TODAY} defaultValue="2026-11-07T22:00" readonly /></Cell>      <Cell name="Disabled"><DateTimeField label="Starts" today={TODAY} defaultValue="2026-11-07T22:00" disabled /></Cell>    </Grid>  )}const TODAY: IsoDate = '2026-11-02'const Cell = (p: { name: string; children?: unknown }) => (  <Stack gap="tight">    <Text role="caption" subtle>{p.name}</Text>    {p.children}  </Stack>)

A maintenance window

Two fields, and the end is checked against the start as either one changes.

tsx
import { state } from '@weft-js/core'import { DateTimeField, Stack, Text } from '@weft-js/warp'import type { IsoDate, IsoDateTime } from '@weft-js/warp'export function WindowPair() {  const from = state<IsoDateTime | null>('2026-11-07T22:00')  const to = state<IsoDateTime | null>('2026-11-08T01:30')  const span = () => (from.value && to.value ? minutesBetween(from.value, to.value) : null)  return (    <Stack gap="group">      <Text role="heading">Maintenance window</Text>      <DateTimeField label="Starts" today={TODAY} value={from} min={TODAY} step={15} />      <DateTimeField label="Ends" today={TODAY} value={to} min={TODAY} step={15} error={(span() ?? 1) <= 0 ? 'End after it starts.' : undefined} />      <Text subtle role="caption">{span() === null ? 'Pick both ends.' : (span() ?? 0) > 0 ? `Checkout is read-only for ${lasting(span()!)}.` : 'The window is empty.'}</Text>    </Stack>  )}const min = 60_000const TODAY: IsoDate = '2026-11-02'const minutesBetween = (a: string, b: string) => Math.round((Date.parse(`${b}Z`) - Date.parse(`${a}Z`)) / 60000)const lasting = (m: number) => (m >= 60 ? `${Math.floor(m / 60)} h${m % 60 ? ` ${m % 60} min` : ''}` : `${m} min`)

When to use

Use it for

  • A single moment, like a deploy or a reminder

Reach for something else

Accessibility

Up/Down
Changes the focused part
Tab
Moves from the calendar to the times
Escape
Closes the panel

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.
value`${number}-${number}-${number}T${number}:${number}` | `${number}-${numbe…—The current value, when you own the state.
defaultValue`${number}-${number}-${number}T${number}:${number}` | `${number}-${numbe…—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.
min`${number}-${number}-${number}`—The lowest allowed value.
max`${number}-${number}-${number}`—The highest allowed value.
isDateUnavailable((d: `${number}-${number}-${number}`) => boolean)—Return true for a day that can't be picked: it shows struck through.
firstDayOfWeek0 | 2 | 3 | 4 | 6 | 1 | 5—The day each calendar row starts on, 0 for Sunday to 6. The locale's otherwise.
stepnumber30How far one key press moves it.
EventTypeDefaultDescription
onInput((e: InputEvent & { currentTarget: HTMLInputElement & HTMLTextAreaElemen…—Called on each keystroke.
onValueChange((v: `${number}-${number}-${number}T${number}:${number}` | `${number}-${…—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.

Parts

Every element carries a stable name, so you can style it from your own CSS: [data-scope="date-time-field"] [data-part="literal"]

literal

RelatedDate fieldTime field