A date and time picker is one of those components that looks small in a design file and turns into a real time sink once you start building it. You need a calendar, a time selector, a state that keeps both in sync, and usually a range mode and a translated version somewhere down the line, too.

This guide walks through ready-made picker patterns you can drop into a React project today: a combined date and time picker, a date range picker, and a time picker.

Every one of these is available as a Shadcn Date Picker component you can install with a single CLI command instead of building from scratch.

These components are built on both Radix and Base UI primitives, and the versions below use Base UI. They also support copy-prompt functionality, so you can paste them straight into v0, Lovable, or Bolt if that's part of your workflow.

Table of Contents

Prerequisites

To follow along, you should know:

  • The basics of React, including useState and props
  • How to install components with the shadcn/ui CLI
  • Basic Tailwind CSS class names

You also need a React project with shadcn/ui already set up. If you haven't done that yet, run the shadcn/ui CLI setup command in your project before continuing.

What You'll Build

  • A DateTimePicker component that combines a calendar and time slots into one value.
  • A TimePicker variant that reuses the same time-slot logic without a calendar.
  • A DateRangePicker that lets a user pick a start and end date.

How to Install a Shadcn Date Time Picker

All the components below install through the same CLI pattern. Pick the package manager you use:

pnpm

pnpm dlx shadcn@latest add @shadcn-space/date-picker-01

npm

npx shadcn@latest add @shadcn-space/date-picker-01

yarn

yarn dlx shadcn@latest add @shadcn-space/date-picker-01

bun

bunx --bun shadcn@latest add @shadcn-space/date-picker-01

Every other component below installs the same way: just swap the package name at the end of the command. If you haven't set up the CLI in your project yet, this getting-started guide covers that first and shows how to integrate these components when you're working through an MCP-connected editor.

How to Build a Date and Time Picker

This is the combined picker: a calendar popover for the date, plus start and end time fields, wrapped around a booking confirmation flow.

Folder structure:

components └── shadcn-space └── date-picker └── date-picker-01.tsx

Component code:

```
"use client";
import { useState } from "react";
import { format } from "date-fns";
import { CalendarIcon, Clock, ChevronDown, Check } from "lucide-react";
import { cn } from "@/lib/utils";

import { Button } from "@/components/ui/button";
import { Calendar } from "@/components/ui/calendar";
import { Input } from "@/components/ui/input";
import { Label } from "@/components/ui/label";
import {
Popover,
PopoverContent,
PopoverTrigger,
} from "@/components/ui/popover";

const DateAndTimePickerDemo = () => {
const [open, setOpen] = useState(false);
const [date, setDate] = useState(undefined);
const [bookingStatus, setBookingStatus] = useState<
"idle" | "loading" | "success"

("idle");

const handleBooking = () => {
setBookingStatus("loading");
setTimeout(() => setBookingStatus("success"), 1500);
};

return (
<>




Select Date


setBookingStatus("idle")}
render={


{date ? format(date, "PPP") : Select a date}


}
/>

{
setDate(d);
setOpen(false);
}}
className="rounded-md border-none"
/>


    <div className="grid grid-cols-2 gap-4">
      <div className="grid gap-2">
        <Label
          htmlFor="time-from"
          className="text-sm font-semibold text-muted-foreground flex items-center gap-1.5"
        >
          <Clock className="size-3.5" /> Start Time
        </Label>
        <Input
          type="time"
          id="time-from"
          defaultValue="09:00"
          className="h-10 bg-background appearance-none transition-all focus:ring-2 focus:ring-primary/20"
        />
      </div>
      <div className="grid gap-2">
        <Label
          htmlFor="time-to"
          className="text-sm font-semibold text-muted-foreground flex items-center gap-1.5"
        >
          <Clock className="size-3.5" /> End Time
        </Label>
        <Input
          type="time"
          id="time-to"
          defaultValue="10:00"
          className="h-10 bg-background appearance-none transition-all focus:ring-2 focus:ring-primary/20"
        />
      </div>
    </div>

    <Button
      className="w-full h-11 font-semibold transition-all group overflow-hidden relative cursor-pointer"
      onClick={handleBooking}
      disabled={!date || bookingStatus !== "idle"}
    >
      {bookingStatus === "idle" && (
        <span className="flex items-center gap-2">Confirm Meet</span>
      )}
      {bookingStatus === "loading" && (
        <div className="flex items-center gap-2">
          <div className="h-4 w-4 animate-spin rounded-full border-2 border-current border-t-transparent" />
          Processing...
        </div>
      )}
      {bookingStatus === "success" && (
        <span className="flex items-center gap-2 animate-in zoom-in-50 duration-300">
          <Check className="h-4 w-4" />
          Meet Scheduled!
        </span>
      )}
    </Button>
  </div>
</>

);
};

export default DateAndTimePickerDemo;
```

How This Component Works

This component combines three pieces of state into a simple booking flow:

The date is stored using React state:

const [date, setDate] = useState<Date | undefined>(undefined);

When a user selects a day from the calendar, the onSelect callback updates the state and closes the popover:

onSelect={(d) => { setDate(d); setOpen(false); }}

The calendar itself lives inside a Popover, which keeps the UI compact. Clicking the trigger button opens the calendar panel:

<Popover open={open} onOpenChange={setOpen}>

The displayed date uses date-fns formatting:

format(date, "PPP")

This converts a JavaScript Date object into a readable format such as:

July 22, 2026

The time fields use native HTML inputs:

<Input type="time" />

Native time inputs provide built-in browser support, mobile pickers, keyboard accessibility, and locale-aware formatting without additional libraries.

The booking button demonstrates how UI state can change during an async action:

"idle" → "loading" → "success"

In a real application, handleBooking() would typically call an API endpoint instead of using setTimeout.

This pattern works well for:

How to Build a Date Range Picker

For anything involving a stay, a rental, or a multi-day booking, you need a range instead of a single date. This component pairs a two-month calendar view with a formatted range label and a computed night count.

Folder structure:

components └── shadcn-space └── date-picker └── date-picker-02.tsx

Install this Shadcn Date Range Picker with:

npx shadcn@latest add @shadcn-space/date-picker-02

Component code:

```
"use client";
import * as React from "react";
import { CalendarIcon, ChevronDown } from "lucide-react";
import { addDays, format } from "date-fns";
import { DateRange } from "react-day-picker";

import { cn } from "@/lib/utils";
import { Button } from "@/components/ui/button";
import { Calendar } from "@/components/ui/calendar";
import {
Popover,
PopoverContent,
PopoverTrigger,
} from "@/components/ui/popover";
import { Label } from "@/components/ui/label";

const DateRangePickerDemo = () => {
const [date, setDate] = React.useState({
from: new Date(),
to: addDays(new Date(), 7),
});

return (



Select Travel Dates



<PopoverTrigger
render={


{date?.from ? (
date.to ? (
<>
{format(date.from, "LLL dd, y")} -{" "}
{format(date.to, "LLL dd, y")}
</>
) : (
format(date.from, "LLL dd, y")
)
) : (
Pick a range
)}


}
/>






{date?.from && date?.to
? Stay duration: ${Math.round((date.to.getTime() - date.from.getTime()) / (1000 * 60 * 60 * 24))} nights
: "Please select a valid date range."}



);
};

export default DateRangePickerDemo;
```

How the Date Range Picker Works

This version switches the Calendar component from single-date mode to range mode:

mode="range"

Instead of storing one Date, the component stores a DateRange object:

{ from: Date, to: Date }

This makes it easy to work with booking systems, hotel stays, travel forms, and rental applications.

The component displays two calendar months:

numberOfMonths={2}

Showing two months reduces navigation and improves the selection experience for longer stays.

The stay duration is calculated directly from the selected dates:

(date.to.getTime() - date.from.getTime())

This avoids additional libraries and keeps the logic simple.

Compared to the first example:

How to Build a Time Picker

Not every form needs a calendar. This one wraps the native in an InputGroup with a clock icon that triggers the browser's own time picker UI on click.

Folder structure:

components └── shadcn-space └── date-picker └── date-picker-03.tsx

Install this Shadcn Time Picker with:

npx shadcn@latest add @shadcn-space/date-picker-03

Component code:

```
"use client";
import { useRef } from "react";
import { Label } from "@/components/ui/label";
import {
InputGroup,
InputGroupAddon,
InputGroupInput,
} from "@/components/ui/input-group";
import { Clock8Icon } from "lucide-react";

const TimePickerWithIconDemo = () => {
const inputRef = useRef(null);

const handleShowPicker = () => {
if (inputRef.current && "showPicker" in inputRef.current) {
try {
inputRef.current.showPicker();
} catch (error) {
console.error("Failed to open native picker:", error);
}
}
};

return (


Select Slot




&::-webkit-calendar-picker-indicator:hidden &::-webkit-calendar-picker-indicator:appearance-none" />


);
};
export default TimePickerWithIconDemo;
```

How the Time Picker Works

This component uses the browser's native time picker instead of building a custom dropdown.

The input is referenced with useRef:

const inputRef = useRef<HTMLInputElement>(null);

This allows the icon button to access the input element directly.

When users click the clock icon, the browser's picker opens programmatically:

inputRef.current.showPicker();

The showPicker() method is a modern browser API that opens the same interface users would see if they clicked the input manually.

The component wraps the input inside an InputGroup:

<InputGroup>

This creates a cleaner layout where the icon behaves as part of the field rather than a separate button.

The browser's default picker icon is hidden:

```

```

This prevents duplicate icons and gives full control over the UI.

The benefit of this approach is that it keeps:

Instead of rebuilding time selection from scratch, the component improves the native experience with custom styling.

Live Preview of the Components

Key Concepts Recap

  • Pick the pattern that matches the data you're collecting: A single date and time for bookings, a range for stays, a bare time field for slots.
  • Every component installs the same way: One CLI command per component: pnpm, npm, yarn, and bun. All are supported. Adding a new picker to a project takes one line, not a manual build.
  • The date and time picker separates date state from time state, then combines them at the confirmation step. This keeps the calendar and the time inputs from getting out of sync.
  • The range picker leans on the Calendar's built-in mode="range": The two-month view and the night count come from the selection state, not custom range math.
  • The time picker wraps the native <input type="time"> instead of replacing it: This keeps native accessibility and mobile keyboard behavior intact.

Conclusion

You now have working picker patterns for React: a date and time picker for bookings, a date range picker for stays, and a plain time picker for slots.

Each one installs with a single CLI command, so you can pick whichever one matches the form you're building and have it running in minutes.

Resources