# Date Time Picker

Edit a local date and time in one control.

Scheduled publish time

[Complete TSX file](/examples/date-time-picker/default.tsx)

```tsx
"use client";

import { DateTimePicker } from "@/siteplane/components/date-time-picker";
import {
  Field,
  FieldDescription,
  FieldLabel,
} from "@/siteplane/components/field";

export default function Example() {
  return (
    <Field className="w-full max-w-sm">
      <FieldLabel>Publish at</FieldLabel>
      <DateTimePicker
        className="w-full"
        defaultValue={{
          date: new Date(2026, 9, 6),
          time: { hour: 9, minute: 0 },
        }}
      />
      <FieldDescription>
        The post goes live at this time in your local time zone.
      </FieldDescription>
    </Field>
  );
}
```

## Installation

Complete [one-time setup](/docs/installation/) first.

```bash
pnpm dlx shadcn@4.18.0 add @siteplane/date-time-picker
```

With npm: `npx shadcn@4.18.0 add @siteplane/date-time-picker`. [Registry JSON](/registry/date-time-picker.json) lists source files and dependencies.

For this page’s examples, also install:

```bash
pnpm dlx shadcn@4.18.0 add @siteplane/button @siteplane/form @siteplane/input
```

Field is installed with Input.

## Usage

```tsx
import { DateTimePicker } from "@/siteplane/components/date-time-picker";

<DateTimePicker aria-label="Meeting start" />;
```

## Controlled value

Pass `value` and `onValueChange` to keep the date and time in React state. `onValueChange` receives `undefined` when the field is cleared; store `null` for an empty controlled value, because `undefined` makes the picker uncontrolled.

Controlled meeting start

[Complete TSX file](/examples/date-time-picker/controlled.tsx)

```tsx
"use client";

import {
  DateTimePicker,
  type DateTimePickerValue,
} from "@/siteplane/components/date-time-picker";
import { useState } from "react";

export default function Example() {
  const [value, setValue] = useState<DateTimePickerValue | null>({
    date: new Date(2026, 8, 7),
    time: { hour: 9, minute: 30 },
  });
  return (
    <DateTimePicker
      aria-label="Meeting start"
      value={value}
      onValueChange={(next) => setValue(next ?? null)}
    />
  );
}
```

## 12-hour clock

Set `hourCycle={12}` for an application that shows times such as 9:00 PM. The time adds an AM/PM segment after the minutes, which takes focus and arrow keys like the other segments; typing A or P sets it. The time selector names each hour with its day period, such as 09 PM. The value stays in 24-hour time, and the form still receives `YYYY-MM-DDTHH:mm`.

```tsx
import { DateTimePicker } from "@/siteplane/components/date-time-picker";

<DateTimePicker aria-label="Snooze until" hourCycle={12} />;
```

## Unavailable dates

Use `calendarProps.disabled` to block past dates and weekends. The calendar doesn't offer these dates, and a typed date that breaks a rule marks the field invalid once it loses focus or the form is submitted; `FieldError` shows the message. Time rules, such as business hours, are not checked, so validate them in your form and again on the server.

Weekday interview

[Complete TSX file](/examples/date-time-picker/constraints.tsx)

```tsx
"use client";

import { DateTimePicker } from "@/siteplane/components/date-time-picker";
import {
  Field,
  FieldDescription,
  FieldError,
  FieldLabel,
} from "@/siteplane/components/field";
import { useEffect, useState } from "react";

export default function Example() {
  // Read today in the browser once the example mounts. A page rendered on a
  // server or at build time would otherwise keep the day it was rendered.
  const [today, setToday] = useState<Date>();
  useEffect(() => setToday(new Date()), []);
  return (
    <Field className="w-full max-w-sm">
      <FieldLabel>Interview</FieldLabel>
      <DateTimePicker
        calendarProps={{
          disabled: [{ before: today }, { dayOfWeek: [0, 6] }],
        }}
        className="w-full"
      />
      <FieldDescription>
        Interviews take place Monday to Friday.
      </FieldDescription>
      <FieldError />
    </Field>
  );
}
```

## Minute step

`minuteStep` sets the minute options in the time selector, here every 15 minutes. Typed minutes are not rounded, so check the step on the server if it matters.

Meeting start in 15-minute steps

[Complete TSX file](/examples/date-time-picker/step.tsx)

```tsx
"use client";

import { DateTimePicker } from "@/siteplane/components/date-time-picker";
import { Field, FieldLabel } from "@/siteplane/components/field";

export default function Example() {
  return (
    <Field className="w-full max-w-sm">
      <FieldLabel>Meeting start</FieldLabel>
      <DateTimePicker
        className="w-full"
        defaultValue={{
          date: new Date(2026, 9, 14),
          time: { hour: 10, minute: 30 },
        }}
        minuteStep={15}
      />
    </Field>
  );
}
```

## Disabled, read-only and invalid

A disabled `Field` also disables the picker, and `readOnly` shows a date and time that can't be changed on the regular field surface. For an error from your application, such as a time outside business hours, set `invalid` on `Field` and explain the problem in a `FieldError`.

Disabled, read-only and invalid date and time fields

[Complete TSX file](/examples/date-time-picker/states.tsx)

```tsx
"use client";

import { DateTimePicker } from "@/siteplane/components/date-time-picker";
import {
  Field,
  FieldDescription,
  FieldError,
  FieldLabel,
} from "@/siteplane/components/field";

export default function Example() {
  return (
    <div className="grid w-full max-w-sm gap-4">
      <Field disabled>
        <FieldLabel>Meeting start</FieldLabel>
        <DateTimePicker
          className="w-full"
          defaultValue={{
            date: new Date(2026, 9, 14),
            time: { hour: 10, minute: 30 },
          }}
        />
      </Field>
      <Field>
        <FieldLabel>Webinar start</FieldLabel>
        <DateTimePicker
          className="w-full"
          defaultValue={{
            date: new Date(2026, 9, 21),
            time: { hour: 16, minute: 0 },
          }}
          readOnly
        />
        <FieldDescription>Set by the organizer.</FieldDescription>
      </Field>
      <Field invalid>
        <FieldLabel>Call start</FieldLabel>
        <DateTimePicker
          className="w-full"
          defaultValue={{
            date: new Date(2026, 9, 16),
            time: { hour: 19, minute: 30 },
          }}
        />
        <FieldError match={true}>
          Choose a time between 09:00 and 17:00.
        </FieldError>
      </Field>
    </div>
  );
}
```

## Submission and reset

With `name`, DateTimePicker submits a hidden local `YYYY-MM-DDTHH:mm` string without seconds, UTC offset or `Z` suffix; convert time zones in your application. The example submits `{ "startsAt": "2026-10-14T10:30" }`, and an empty field submits an empty string. Changing `locale` changes only the display. With `required`, submitting an empty field is blocked, the field is focused and `FieldError` shows the required message. Native form reset restores `defaultValue`; reset controlled state yourself.

Date and time in a form

[Complete TSX file](/examples/date-time-picker/form.tsx)

```tsx
"use client";

import { Button } from "@/siteplane/components/button";
import { DateTimePicker } from "@/siteplane/components/date-time-picker";
import { Field, FieldError, FieldLabel } from "@/siteplane/components/field";
import { Form } from "@/siteplane/components/form";

export default function Example() {
  return (
    <Form
      className="grid w-full max-w-sm gap-4"
      onSubmit={(event) => {
        event.preventDefault();
      }}
    >
      <Field>
        <FieldLabel>Starts at</FieldLabel>
        <DateTimePicker
          className="w-full"
          defaultValue={{
            date: new Date(2026, 9, 14),
            time: { hour: 10, minute: 30 },
          }}
          name="startsAt"
          required
        />
        <FieldError />
      </Field>
      <div className="flex gap-2">
        <Button type="submit">Submit</Button>
        <Button type="reset" variant="outline">
          Reset
        </Button>
      </div>
    </Form>
  );
}
```

## API reference

| Prop | Type | Default | Description |
| --- | --- | --- | --- |
| aria-label | string | unset | Accessible name when no FieldLabel or aria-labelledby names the picker. |
| aria-labelledby / aria-describedby | string | unset | External label and help ids; inside a Field, FieldLabel, FieldDescription and FieldError are connected automatically. |
| aria-invalid | AriaAttributes[aria-invalid] | unset | External invalid state; while set, it replaces the built-in date checks. |
| value / defaultValue | DateTimePickerValue \| null (value only) | uncontrolled / unset | Controlled or initial value; null is an empty controlled value. |
| onValueChange | (value: DateTimePickerValue \| undefined) => void | unset | Reports a complete date and time, or undefined when cleared. |
| disabled | boolean | false | Prevents editing/opening; a disabled Field also disables the picker. |
| className | string | unset | Outer control styling; the picker keeps its content width unless a width utility such as w-full is set. |
| locale | string | context | Formatting locale, not time zone. |
| calendarProps | CalendarOptions | unset | The calendar options below: defaultMonth, disabled, startMonth, endMonth and todayDate. |
| readOnly / required | boolean | false | Read-only field on the regular surface; required blocks form submission while empty, focuses the field and shows the required message in FieldError. |
| size | sm \| md \| lg \| xl | provider / lg | Control size. |
| textSize | sm \| base | unset | Field text size; unset is 14px, or 16px at xl, like Input. |
| variant | default \| elevated \| inset \| soft | default | Input surface. |
| form | string | unset | Associated form ID. |
| name | string | unset | Hidden serialized input name; a Field name takes precedence. |
| minuteStep | number | 5 | Minute selector options; normalized to an integer between 1 and 60. |
| hourCycle | 12 \| 24 | 24 | Clock of the time segments and the time selector; 12 adds the locale's AM/PM segment. The value stays in 24-hour time. |
| openDatePickerLabel / openTimePickerLabel | string | Open date calendar / Open time selector | Picker button labels. |
| incompleteLabel | string | Enter a complete date and time. | The error of a required picker while its date and time are incomplete, shown by FieldError. |

`size` and `variant` use the same scale and surfaces as Input; see [Input sizes](/docs/components/input/#sizes) and [variants](/docs/components/input/#variants).

### Calendar options

`calendarProps` takes `CalendarOptions`, the type of the same options on [Calendar](/docs/components/calendar/#calendar-options). `startMonth` and `endMonth` (Date, unset) limit the calendar to the first and last allowed months. `disabled` (unset) makes days unavailable with one matcher or a list of matchers, each a Date, a `(date: Date) => boolean` test or a rule with `after`, `before`, `from`, `to` and `dayOfWeek` (0 Sunday through 6 Saturday); `true` disables the whole calendar. Both apply to picking in the calendar and to typed dates.
`defaultMonth` (Date, unset) sets the month the calendar shows when it opens; while it is set, it also wins over the month of the selected date. `todayDate` (Date, the device's date) sets the day the calendar marks as today; without a selected date, the calendar opens on its month, and typed segments start from it.

Exported types: `DateTimePickerProps`, `DateTimePickerValue`. These describe the contracts above.

[Read the installed source](/source/date-time-picker).

## Usage notes

Values use native Date calendar fields in the local runtime time zone. Locale changes formatting, not the zone. DateTimePickerValue is `{date:Date,time:{hour,minute}}`; no time-zone selector or automatic zone conversion is exposed. The time button stays disabled until a date is set, and the time selector's labels (Hours, Minutes) are English. Inside a Field, FieldLabel names DateTimePicker and clicking it focuses the first segment; outside a Field, pass `aria-label` or `aria-labelledby`. Arrow keys change the focused date or time segment. Only the props listed above are supported, and DateTimePicker renders its own segments, buttons and popovers; it accepts no children or ref. The named Props type and value type are exported from the component module.

The date and the time, with the date button between them, read in the direction of the locale, which the arrow keys follow too, and are isolated from the text around them. An `en-US` value keeps its date before its time in a right-to-left page, while the field keeps the page's direction: its time button sits at the page's inline end.
