DateTimePicker
A combined date + time field — a calendar and a time field in one popover, with a single Date value.
@garn/ui/date-time-pickerDefault
A default date + time field.
Twelve Hour
A 12-hour format with a custom date-time pattern.
States
Disabled and invalid.
Installation
garn is copy-in — the garn CLI writes this component's source into your project, so you own and can edit it. It resolves any base files and installs the npm dependencies below for you.
npx garn-ui add date-time-picker- Registry
date-time-picker- Source
- date-time-picker.tsx
- Deps
date-fnsclass-variance-authority- Registry deps
utilsdate-iofield-surfaceuse-controllable-stateuse-merged-refdate-pickertime-fieldcalendar
Import
import { DateTimePicker } from "@garn/ui/date-time-picker";Props
Generated from the component's TypeScript types.
appearance- Description
- No description.
- Type
"ghost" | "outline" | "soft"- Default
outline
size- Description
- No description.
- Type
"lg" | "md" | "sm" | "xl" | "xs"- Default
md
"aria-invalid"- Description
- No description.
- Type
"false" | "true" | boolean
"aria-label"- Description
- No description.
- Type
string
"aria-labelledby"- Description
- No description.
- Type
string
className- Description
- No description.
- Type
string
clearable- Description
- No description.
- Type
boolean
clearLabel- Description
- No description.
- Type
string
contentClassName- Description
- No description.
- Type
string
defaultOpen- Description
- No description.
- Type
boolean
defaultValue- Description
- No description.
- Type
Date
disabled- Description
- No description.
- Type
boolean
format- Description
- Field format (date + time tokens).
- Type
string
id- Description
- No description.
- Type
string
isDateUnavailable- Description
- No description.
- Type
(date: Date) => boolean
maxDate- Description
- No description.
- Type
Date
minDate- Description
- No description.
- Type
Date
name- Description
- Name for a hidden input mirroring the value on form submit as a LOCAL date-time `yyyy-MM-dd'T'HH:mm:ss` with no Z/offset (the `<input type="datetime-local">` wire format). Serializing a UTC instant is a consumer decision — convert the `Date` yourself if you truly mean one.
- Type
string
onOpenChange- Description
- No description.
- Type
(open: boolean) => void
onValueChange- Description
- No description.
- Type
(value: Date | undefined) => void
open- Description
- No description.
- Type
boolean
placeholder- Description
- No description.
- Type
string
readOnly- Description
- No description.
- Type
boolean
ref- Description
- No description.
- Type
React.Ref<HTMLInputElement>
required- Description
- No description.
- Type
boolean
step- Description
- Minutes stepped by the TimeField arrow keys.
- Type
number
timeFormat- Description
- Format for the inner TimeField.
- Type
string
triggerLabel- Description
- No description.
- Type
string
value- Description
- No description.
- Type
Date
Plus 3 inherited native <input> attributes.
Styling
Target these data-slots and remap these tokens to restyle without forking the component.
[data-slot="date-time-picker"][data-slot="date-time-picker-input"][data-slot="date-time-picker-trigger"][data-slot="date-time-picker-clear"]| Property | Token | Tier |
|---|---|---|
height | --garn-control-h-md | semantic · air |
paddingInline | --garn-control-px-md | semantic · air |
Overview
DateTimePicker is the combined date + time control. One typeable field opens a Popover that pairs the Calendar (date) with a TimeField (time): picking a day keeps the current time, editing the time keeps the day, and the committed value is a single native Date carrying both. A format prop (date + time tokens like MM/dd/yyyy HH:mm) drives the field's display/parse/placeholder; timeFormat drives the inner time field.
It's built on the same field + popover shell as DatePicker (and reuses TimeField), so it matches the rest of the form family. Use DatePicker when there's no time, and TimeField/TimePicker when there's no date.
Guidelines
Pair with a Label and choose a format that shows both parts (date + time). Constrain the date with minDate/maxDate/isDateUnavailable. The field is fully keyboard-operable (ArrowDown opens, Enter commits, Escape closes); inside the popover the calendar and the time field are separate, each operable on its own. Reflect validity with aria-invalid + a described message.
Wire format: local wall time, no Z. With name, the hidden form input posts yyyy-MM-dd'T'HH:mm:ss (the <input type="datetime-local"> wire format) — exactly what the user expressed, never silently converted to UTC. If your backend wants a UTC instant, convert the Date from onValueChange yourself; that's a consumer decision, not the library default.
Best practices
- Pair with a label; choose a combined
formatand a matchingtimeFormat. - Constrain the date with min/max/isDateUnavailable.
- Hold the value in state; read it as one
Date.
- Don't rely on the placeholder as the label.
- Don't split date and time into two separate values — the point is one
Date.
Content guidelines
- Label with what the timestamp is for ("Starts at").
- Use a
formatthat reads unambiguously ("MMM d, yyyy · h:mm a").
Accessibility
- Role
textbox- ARIA APG
- date-picker-dialog
- Focus
- Field focus surface as the date pickers; on open, focus enters the calendar; the time field is a separate tab stop in the popover.
Enforceable contracts
Give every date-time picker an accessible label.
when no associated <label htmlFor>, aria-label, or aria-labelledby
Keyboard
| ArrowDown | Open the calendar + time popover. |
| Enter | Commit the typed date-time. |
| Escape | Close the popover. |
| ArrowUpArrowDown | Step the time (inside the time field) by `step` minutes. |
- • Give the field an accessible label; the format-pattern placeholder is not a label.
- • The popover pairs a labeled calendar grid with a labeled time field; each is independently operable.
- • Reflect validation with aria-invalid + a described message.
- Status
- Stable
- Version
v0.3.0- Introduced
v0.2.0
garn doesn't yet record a per-release version history. This reflects the component's current lifecycle state; full changelog entries will appear once the metadata pipeline emits a lifecycle.history field.