Shadcn DateTime Picker

July 4, 2026 ยท View on GitHub

A beautifully crafted, fully-featured datetime picker built on top of shadcn/ui, @daypicker/react v10, and Base UI. Ships with 3 variants to cover every use case.

Variants

ComponentUse Case
DateTimePickerAll-in-one calendar + time picker in a single popover
DateTimePickerSeparateIndividual date, time, and timezone fields
DateTimePickerRangeStart/end date+time range selection
DateTimePickerFieldReact Hook Form + Zod integration with Field component

Features

  • ๐Ÿ“… Date & time selection โ€” calendar + scrollable time picker side-by-side
  • ๐Ÿ• 12h / 24h format โ€” AM/PM or military time
  • ๐ŸŒ Timezone selector โ€” grouped IANA timezones with offset display
  • โฐ Min/Max time โ€” restrict to business hours or custom ranges
  • ๐Ÿ“† Min/Max dates โ€” constrain selectable date range
  • ๐Ÿšซ Disabled dates โ€” custom function to disable specific dates (weekends, holidays, etc.)
  • ๐Ÿ•‘ Past time auto-disable โ€” when today is selected, past time slots are greyed out
  • ๐Ÿ“ฑ Responsive โ€” Popover on desktop, Drawer on mobile
  • ๐Ÿ“ Form compatible โ€” name prop with hidden input, ref forwarding via forwardRef
  • ๐Ÿ”„ Controlled & uncontrolled โ€” works both ways
  • ๐Ÿ—“๏ธ Week start day โ€” start week on any day (Sunday, Monday, etc.)
  • โฑ๏ธ Time intervals โ€” 15, 30, or 60 minute slots
  • ๐ŸŽฏ Scroll to selected โ€” time list auto-scrolls to selected time on open
  • ๐ŸŒ™ Dark mode โ€” inherits from your Shadcn theme
  • ๐Ÿ”’ SSR safe โ€” no hydration mismatches with Next.js
  • ๐Ÿ“ฆ Zero extra dependencies โ€” only uses what Shadcn already provides

Tech Stack

  • React 19 + Next.js 16
  • Shadcn/ui v4 (Base UI)
  • @daypicker/react v10
  • date-fns v4
  • Tailwind CSS v4
  • TypeScript

Installation

Prerequisites

You need a project with shadcn/ui already set up.

1. Install dependencies

npm install @daypicker/react date-fns

2. Add required Shadcn components

npx shadcn@latest add popover button scroll-area select separator drawer

3. Copy the components

Copy the following files into your project:

src/components/
โ”œโ”€โ”€ ui/calendar-dropdown.tsx       # Required โ€” custom calendar with dropdown navigation
โ”œโ”€โ”€ datetime-picker.tsx            # Variant 1 โ€” combined picker
โ”œโ”€โ”€ datetime-picker-separate.tsx   # Variant 2 โ€” separate fields
โ”œโ”€โ”€ datetime-picker-range.tsx      # Variant 3 โ€” range picker
โ””โ”€โ”€ datetime-picker-form.tsx       # Variant 4 โ€” React Hook Form + Field wrapper

Pick only the variants you need โ€” each file is self-contained.


DateTimePicker

All-in-one calendar + time picker in a single popover. Best for forms, scheduling, and general datetime input.

Basic Usage

import { DateTimePicker } from "@/components/datetime-picker";

export default function MyForm() {
  const [date, setDate] = useState<Date>();

  return <DateTimePicker value={date} onChange={setDate} />;
}

12-Hour Format

<DateTimePicker hourFormat={12} />

Business Hours Only

<DateTimePicker
  minTime="09:00"
  maxTime="17:00"
  hourFormat={12}
  timeInterval={30}
/>

With Timezone Selector

<DateTimePicker showTimezone />

// Pre-set timezone
<DateTimePicker showTimezone timezone="America/New_York" />

// Track timezone changes
<DateTimePicker
  showTimezone
  onTimezoneChange={(tz) => console.log(tz)}
/>

Date Restrictions

// Next 30 days only
<DateTimePicker
  minDate={new Date()}
  maxDate={new Date(Date.now() + 30 * 24 * 60 * 60 * 1000)}
/>

// Disable weekends
<DateTimePicker
  disabledDates={(date) => date.getDay() === 0 || date.getDay() === 6}
/>

Week Starts on Monday

<DateTimePicker weekStartsOn={1} />

Booking / Appointment Scenario

<DateTimePicker
  minDate={new Date()}
  maxDate={new Date(Date.now() + 60 * 24 * 60 * 60 * 1000)}
  minTime="09:00"
  maxTime="17:00"
  hourFormat={12}
  timeInterval={30}
  weekStartsOn={1}
  showTimezone
  disabledDates={(date) => date.getDay() === 0 || date.getDay() === 6}
  placeholder="Book an appointment"
/>

Form Integration

// With React Hook Form
<DateTimePicker
  name="appointment"
  ref={register}
  value={watch("appointment")}
  onChange={(date) => setValue("appointment", date)}
/>

// Native form
<form onSubmit={handleSubmit}>
  <DateTimePicker name="datetime" />
  <button type="submit">Submit</button>
</form>

API Reference

PropTypeDefaultDescription
valueDate | undefinedโ€”Controlled selected date/time
onChange(date: Date | undefined) => voidโ€”Callback when date/time changes
disabledbooleanfalseDisable the picker
placeholderstring"Pick a date and time"Placeholder text
hourFormat12 | 2424Time display format
timeInterval15 | 30 | 6015Minutes between time slots
minDateDateโ€”Earliest selectable date
maxDateDateโ€”Latest selectable date
minTimestringโ€”Earliest time slot (e.g. "09:00")
maxTimestringโ€”Latest time slot (e.g. "17:00")
timezonestringUser's localIANA timezone (e.g. "America/New_York")
showTimezonebooleanfalseShow timezone selector dropdown
onTimezoneChange(tz: string) => voidโ€”Callback when timezone changes
weekStartsOn0-6โ€”Week start day (0=Sun, 1=Mon, ...)
disabledDates(date: Date) => booleanโ€”Custom date disable function
namestringโ€”Form field name (renders hidden input)
classNamestringโ€”Custom class for trigger button
refRef<HTMLInputElement>โ€”Ref forwarded to hidden input

DateTimePickerSeparate

Individual date, time, and timezone fields that work together. Best when you want each field to be independently accessible.

Basic Usage

import { DateTimePickerSeparate } from "@/components/datetime-picker-separate";

<DateTimePickerSeparate />

With Timezone

<DateTimePickerSeparate showTimezone hourFormat={12} />

Business Hours

<DateTimePickerSeparate
  minTime="09:00"
  maxTime="17:00"
  hourFormat={12}
  timeInterval={30}
  showTimezone
  disabledDates={(date) => date.getDay() === 0 || date.getDay() === 6}
/>

API Reference

Same props as DateTimePicker, plus:

PropTypeDefaultDescription
datePlaceholderstring"Pick a date"Date field placeholder
timePlaceholderstring"Pick time"Time field placeholder

DateTimePickerRange

Start/end date+time range selection with tab-based navigation. Best for booking systems, event scheduling, and analytics filters.

Basic Usage

import { DateTimePickerRange, type DateTimeRange } from "@/components/datetime-picker-range";

const [range, setRange] = useState<DateTimeRange>();

<DateTimePickerRange value={range} onChange={setRange} />

Meeting Scheduler

<DateTimePickerRange
  hourFormat={12}
  minTime="09:00"
  maxTime="17:00"
  timeInterval={30}
  showTimezone
  minDate={new Date()}
  disabledDates={(date) => date.getDay() === 0 || date.getDay() === 6}
  placeholder="Schedule a meeting"
/>

How It Works

  1. Click the trigger to open the picker
  2. From tab โ€” select start date and time
  3. Selecting a start date automatically switches to the To tab
  4. To tab โ€” select end date and time (dates before start are disabled)
  5. Selecting end time closes the picker

API Reference

PropTypeDefaultDescription
valueDateTimeRangeโ€”{ from?: Date, to?: Date }
onChange(range: DateTimeRange) => voidโ€”Callback with range

All other props are the same as DateTimePicker.


DateTimePickerField

React Hook Form integration using Shadcn's Field component. Handles validation, labels, descriptions, and error display.

Basic Usage

import {
  DateTimePickerField,
  useForm,
  zodResolver,
} from "@/components/datetime-picker-form";
import { z } from "zod";

const schema = z.object({
  appointment: z.date({ error: "Please select a date and time." }),
});

export default function MyForm() {
  const form = useForm({
    resolver: zodResolver(schema),
  });

  return (
    <form onSubmit={form.handleSubmit((data) => console.log(data))}>
      <DateTimePickerField
        control={form.control}
        name="appointment"
        label="Appointment"
        description="Select a date and time for your appointment."
        hourFormat={12}
        minTime="09:00"
        maxTime="17:00"
        timeInterval={30}
      />
      <button type="submit">Submit</button>
    </form>
  );
}

API Reference

PropTypeDefaultDescription
controlControl<T>requiredReact Hook Form control
namePath<T>requiredField name (type-safe)
labelstringโ€”Field label
descriptionstringโ€”Help text below the picker

All other props from DateTimePicker are supported.


Responsive Behavior

All variants automatically adapt to screen size:

  • Desktop (โ‰ฅ768px) โ€” Opens as a Popover below the trigger
  • Mobile (<768px) โ€” Opens as a bottom Drawer with swipe-to-dismiss

No configuration needed.

License

MIT