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/warpUsage
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
- DateField, when the time does not matter
Accessibility
- Up/Down
- Changes the focused part
- Tab
- Moves from the calendar to the times
- Escape
- Closes the panel
API Reference
| Prop | Type | Default | Description |
|---|---|---|---|
label | string | — | The accessible name, drawn as the floating border label (or above, classic). Required. |
hint | unknown | — | One line of help under the control. |
error | unknown | — | Shown instead of the hint; marks the field invalid. |
icon | IconRef | — | Leading icon: it sweeps with the border. |
prefix | unknown | — | Text or an element inside the field before the value, such as $. |
suffix | unknown | — | 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. |
clearable | boolean | false | A clear button (shown once there is a value). |
counter | number | — | Maximum length, shown as a counter. |
id | string | — | The element id. One is made for you if you leave it out. |
disabled | boolean | false | Turns it off, and takes it out of the tab order. |
readonly | boolean | false | Shows the value and lets it be selected and copied, but not changed. |
required | boolean | false | The form will not submit without it. |
name | string | — | The name it posts under in a form. |
locale | string | — | The language and region used to format values. |
today | string | — | Pins "today" (server render, tests); the browser's clock otherwise. |
presentation | Presentation | — | 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. |
open | boolean | State<boolean> | — | A state box that holds whether it is open. |
defaultOpen | boolean | false | Starts 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. |
firstDayOfWeek | 0 | 2 | 3 | 4 | 6 | 1 | 5 | — | The day each calendar row starts on, 0 for Sunday to 6. The locale's otherwise. |
step | number | 30 | How far one key press moves it. |
| Event | Type | Default | Description |
|---|---|---|---|
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