A date and a time of day on one row, each with its own popup; the range variant pairs a start and end.
DateTimeField composes DateField and TimeField into one control. The parts join as "YYYY-MM-DDTHH:MM" (local, no time zone) — pair with a stored zone when the moment matters across zones; keep it naive when the user types the time in their own time ("show starts 7 pm on Saturday" is the same string whether they're in Sydney or Perth).
min and max are also DateTime strings, so a bound on "2026-10-14T17:00" rules out both a later date and a later time on that same date. The TimeField's own bound widens on dates strictly inside the range and only narrows on the bound dates themselves.
DateTimeRangeField pairs two DateTimeFields with an arrow between them. The start caps the end's min (and vice-versa), so an impossible pick is simply not offered — and if the parent passes an inverted pair, the end sub-group gets a soft danger wash so the row disagrees with itself until it's fixed.
Installation
npx shadcn@latest add KezLahd/rime/date-time-fieldAlso installs: base, date-field, time-field.
Usage
import { DateTimeField, DateTimeRangeField, type DateTime, type DateTimeRange } from "@/components/ui";
<DateTimeField value={at} onChange={setAt} format="12h" step={15} />Examples
Date-time and range
API
Generated from the component's TypeScript types and JSDoc (scripts/gen-ui-registry.mjs), so it always matches the source.
DateTimeField
| Prop | Type | Default | Description |
|---|---|---|---|
| valueRequired | DateTime | null | ||
| onChangeRequired | (value: DateTime | null) => void | ||
| format | "12h" | "24h" | "24h" | 24h is the default. 12h shows "9:30 AM" and parses am/pm. |
| step | number | 15 | Minute granularity for the time popup (default 15). |
| min | DateTime | ||
| max | DateTime | ||
| disabled | boolean | ||
| className | string | ||
| aria-label-date | string | aria-label for the date field (default "Date"). | |
| aria-label-time | string | aria-label for the time field (default "Time"). |
DateTimeRangeField
| Prop | Type | Default | Description |
|---|---|---|---|
| valueRequired | DateTimeRange | ||
| onChangeRequired | (value: DateTimeRange) => void | ||
| format | "12h" | "24h" | "24h" | |
| step | number | 15 | |
| disabled | boolean | ||
| className | string |
Theme tokens
Per-component tokens are undeclared by default, so the default shown is the fallback the CSS uses. Set one at :root (or in theme.css) to restyle every instance. Live values are read from this page, so they follow the Rime Studio.
| Token | Default | Live value |
|---|---|---|
| --wash-danger-subtle | rgba(var(--rgb-danger), 0.06) | … |
System tokens it reads
| Token | Default | Live value |
|---|---|---|
| --ink-muted | tokens.css | … |
| --r-md | tokens.css | … |
| --rgb-danger | tokens.css | … |
Accessibility
- Each piece keeps its own field semantics: the DateField accepts DD/MM/YYYY typing, the TimeField accepts spoken-style times, and both expose their own aria-describedby reason when parsing fails.
- The TimeField is disabled until a date is set — a time without a day is ambiguous — so keyboard users aren't trapped typing a time that can't be stored.
Do and don't
Do
- Use this when both pieces matter and the user types them in their own time (events, bookings, deadlines).
- Pass min and max as full DateTime strings so the TimeField narrows automatically on the boundary dates.
Don't
- Don't use it for a cross-zone instant; store the time zone alongside the value, or pass a UTC string your layer above knows to translate.
For AI agents
This page as markdown, rendered from the same registry entry. Point an agent at it (or add .md to this page's URL), or at /llms.txt for the index.
Open date-time-field.mdRelated: date-field, time-field, date-range-filter