DateTimePicker
A combined date + time field — a calendar and a time field in one popover, with a single Date value.
@garn/ui/date-time-pickerOn this page
Default
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- 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
The component's public props and their 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
- Show a clear button when a value is set.
- Type
boolean- Default
true
clearLabel- Description
- Accessible name for the clear button.
- Type
string- Default
"Clear date and time"
contentClassName- Description
- No description.
- Type
string
defaultOpen- Description
- Whether the popover starts open (uncontrolled).
- Type
boolean- Default
false
defaultValue- Description
- Initial date-time (uncontrolled).
- Type
Date
disabled- Description
- Disable the field and block opening.
- Type
boolean- Default
false
format- Description
- Field format (date + time tokens).
- Type
string- Default
"MM/dd/yyyy HH:mm"
id- Description
- No description.
- Type
string
isDateUnavailable- Description
- Per-date disabling (holidays, blackout dates); also blocks typed entry.
- Type
(date: Date) => boolean
maxDate- Description
- Upper selectable bound (inclusive, day granularity).
- Type
Date
minDate- Description
- Lower selectable bound (inclusive, day granularity).
- 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
- Fires when the popover opens or closes.
- Type
(open: boolean) => void
onValueChange- Description
- Fires with the committed date-time (or `undefined` when cleared).
- Type
(value: Date | undefined) => void
open- Description
- Popover open state (controlled).
- Type
boolean
placeholder- Description
- Field text when nothing is selected (defaults to the `format` string).
- Type
string
readOnly- Description
- Render read-only — no editing or opening.
- Type
boolean- Default
false
ref- Description
- No description.
- Type
React.Ref<HTMLInputElement>
required- Description
- Mark the field required for form validation.
- Type
boolean
step- Description
- Minutes stepped by the TimeField arrow keys.
- Type
number- Default
1
timeFormat- Description
- Format for the inner TimeField.
- Type
string- Default
"HH:mm"
triggerLabel- Description
- Accessible name for the trigger that opens the calendar.
- Type
string- Default
"Open calendar"
value- Description
- Selected date-time as a single `Date` (controlled).
- Type
Date
Plus 3 inherited native <input> attributes.
Styling
Target these data-slots and states, 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"]States
| Selector | State | Description |
|---|---|---|
:hover | hover | Edge / fill grows slightly stronger. |
:focus | focus | Border darkens on any focus. |
:focus-visible | focus-visible | Keyboard focus adds a neutral ring. |
[aria-invalid=true] | invalid | Danger border (+ danger ring on focus). |
[disabled] | disabled | Dims and blocks typing; the toggle disables too. |
| Property | Token | Tier |
|---|---|---|
height | --garn-control-h-md | semantic · air |
paddingInline | --garn-control-px-md | semantic · air |
When to use
Collect a date and a time together as one timestamp (event start, deadline, appointment).
Reach for something else when
- Date only (use DatePicker)
- time only (use TimeField/TimePicker)
- a range of date-times (compose two, or use DateRangePicker for date-only ranges).
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").
Troubleshooting
Picking a date resets the time to midnight.
Cause. There was no time yet, so the first date pick defaults the time to 00:00.
Fix. Set the time via the inner time field (it's preserved on subsequent date picks).
Typed date-time snaps back.
Cause. The text didn't match format (which includes the time tokens).
Fix. Type in the exact format shown as the placeholder.
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.
Accessibility requirements
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.