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
| Component | Use Case |
|---|---|
DateTimePicker | All-in-one calendar + time picker in a single popover |
DateTimePickerSeparate | Individual date, time, and timezone fields |
DateTimePickerRange | Start/end date+time range selection |
DateTimePickerField | React 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 โ
nameprop with hidden input,refforwarding viaforwardRef - ๐ 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
| Prop | Type | Default | Description |
|---|---|---|---|
value | Date | undefined | โ | Controlled selected date/time |
onChange | (date: Date | undefined) => void | โ | Callback when date/time changes |
disabled | boolean | false | Disable the picker |
placeholder | string | "Pick a date and time" | Placeholder text |
hourFormat | 12 | 24 | 24 | Time display format |
timeInterval | 15 | 30 | 60 | 15 | Minutes between time slots |
minDate | Date | โ | Earliest selectable date |
maxDate | Date | โ | Latest selectable date |
minTime | string | โ | Earliest time slot (e.g. "09:00") |
maxTime | string | โ | Latest time slot (e.g. "17:00") |
timezone | string | User's local | IANA timezone (e.g. "America/New_York") |
showTimezone | boolean | false | Show timezone selector dropdown |
onTimezoneChange | (tz: string) => void | โ | Callback when timezone changes |
weekStartsOn | 0-6 | โ | Week start day (0=Sun, 1=Mon, ...) |
disabledDates | (date: Date) => boolean | โ | Custom date disable function |
name | string | โ | Form field name (renders hidden input) |
className | string | โ | Custom class for trigger button |
ref | Ref<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:
| Prop | Type | Default | Description |
|---|---|---|---|
datePlaceholder | string | "Pick a date" | Date field placeholder |
timePlaceholder | string | "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
- Click the trigger to open the picker
- From tab โ select start date and time
- Selecting a start date automatically switches to the To tab
- To tab โ select end date and time (dates before start are disabled)
- Selecting end time closes the picker
API Reference
| Prop | Type | Default | Description |
|---|---|---|---|
value | DateTimeRange | โ | { 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
| Prop | Type | Default | Description |
|---|---|---|---|
control | Control<T> | required | React Hook Form control |
name | Path<T> | required | Field name (type-safe) |
label | string | โ | Field label |
description | string | โ | 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