How to Build a Reusable Date-Time Picker in React with shadcn/ui — Opportunihub
Course Remote

How to Build a Reusable Date-Time Picker in React with shadcn/ui

Vaibhav Gupta · Remote

At a glance

Type
Course
Organisation
Vaibhav Gupta
Location
Remote
Work mode
Remote
Deadline
Rolling / not stated
Posted
22 Jul 2026

About this course

<p>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.</p> <p>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.</p> <p>Every one of these is available as a <a href="https://shadcnspace.com/components/date-picker"><strong>Shadcn Date Picker</strong></a> component you can install with a single CLI command instead of building from scratch.</p> <p>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.</p> <h2 id="heading-table-of-contents"><strong>Table of Contents</strong></h2> <ul> <li><p><a href="#heading-prerequisites">Prerequisites</a></p> </li> <li><p><a href="#heading-what-youll-build">What You'll Build</a></p> </li> <li><p><a href="#heading-how-to-install-a-shadcn-date-time-picker">How to Install a Shadcn Date Time Picker</a></p> </li> <li><p><a href="#heading-how-to-build-a-date-and-time-picker">How to Build a Date and Time Picker</a></p> </li> <li><p><a href="#heading-how-to-build-a-date-range-picker">How to Build a Date Range Picker</a></p> </li> <li><p><a href="#heading-how-to-build-a-time-picker">How to Build a Time Picker</a></p> </li> <li><p><a href="#heading-live-preview-of-the-components">Live Preview of the components</a></p> </li> <li><p><a href="#heading-key-concepts-recap">Key Concepts Recap</a></p> </li> <li><p><a href="#heading-conclusion">Conclusion</a></p> </li> <li><p><a href="#heading-resources">Resources</a></p> </li> </ul> <h2 id="heading-prerequisites">Prerequisites</h2> <p>To follow along, you should know:</p> <ul> <li><p>The basics of React, including <code>useState</code> and props</p> </li> <li><p>How to install components with the shadcn/ui CLI</p> </li> <li><p>Basic Tailwind CSS class names</p> </li> </ul> <p>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.</p> <h2 id="heading-what-youll-build">What You'll Build</h2> <ul> <li><p>A <code>DateTimePicker</code> component that combines a calendar and time slots into one value.</p> </li> <li><p>A <code>TimePicker</code> variant that reuses the same time-slot logic without a calendar.</p> </li> <li><p>A <code>DateRangePicker</code> that lets a user pick a start and end date.</p> </li> </ul> <h2 id="heading-how-to-install-a-shadcn-date-time-picker"><strong>How to Install a Shadcn Date Time Picker</strong></h2> <p>All the components below install through the same CLI pattern. Pick the package manager you use:</p> <p><strong>pnpm</strong></p> <pre><code class="language-javascript">pnpm dlx shadcn@latest add @shadcn-space/date-picker-01 </code></pre> <p><strong>npm</strong></p> <pre><code class="language-javascript">npx shadcn@latest add @shadcn-space/date-picker-01 </code></pre> <p><strong>yarn</strong></p> <pre><code class="language-javascript">yarn dlx shadcn@latest add @shadcn-space/date-picker-01 </code></pre> <p><strong>bun</strong></p> <pre><code class="language-javascript">bunx --bun shadcn@latest add @shadcn-space/date-picker-01 </code></pre> <p>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 <a href="https://shadcnspace.com/docs/getting-started/how-to-use-shadcn-cli"><strong>getting-started guide</strong></a> covers that first and shows how to integrate these components when you're working through an MCP-connected editor.</p> <h2 id="heading-how-to-build-a-date-and-time-picker"><strong>How to Build a Date and Time Picker</strong></h2> <p>This is the combined picker: a calendar popover for the date, plus start and end time fields, wrapped around a booking confirmation flow.</p> <p><strong>Folder structure:</strong></p> <pre><code class="language-javascript">components └── shadcn-space └── date-picker └── date-picker-01.tsx </code></pre> <p><strong>Component code:</strong></p> <pre><code class="language-javascript">"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 = () =&gt; { const [open, setOpen] = useState(false); const [date, setDate] = useState&lt;Date | undefined&gt;(undefined); const [bookingStatus, setBookingStatus] = useState&lt; "idle" | "loading" | "success" &gt;("idle"); const handleBooking = () =&gt; { setBookingStatus("loading"); setTimeout(() =&gt; setBookingStatus("success"), 1500); }; return ( &lt;&gt; &lt;div className="grid gap-6"&gt; &lt;div className="grid gap-2"&gt; &lt;Label htmlFor="date" className="text-sm font-semibold"&gt; Select Date &lt;/Label&gt; &lt;Popover open={open} onOpenChange={setOpen}&gt; &lt;PopoverTrigger onPointerDown={() =&gt; setBookingStatus("idle")} render={ &lt;Button variant="outline" id="date" className={cn( "w-full justify-start text-left font-normal h-10 transition-all hover:bg-muted/50 cursor-pointer", !date &amp;&amp; "text-muted-foreground", )} &gt; &lt;CalendarIcon className="mr-2 h-4 w-4 opacity-70" /&gt; {date ? format(date, "PPP") : &lt;span&gt;Select a date&lt;/span&gt;} &lt;ChevronDown className="ml-auto h-4 w-4 opacity-50" /&gt; &lt;/Button&gt; } /&gt; &lt;PopoverContent className="w-auto p-0 border-muted-foreground/10 shadow-2xl" align="start" &gt; &lt;Calendar mode="single" selected={date} onSelect={(d) =&gt; { setDate(d); setOpen(false); }} className="rounded-md border-none" /&gt; &lt;/PopoverContent&gt; &lt;/Popover&gt; &lt;/div&gt; &lt;div className="grid grid-cols-2 gap-4"&gt; &lt;div className="grid gap-2"&gt; &lt;Label htmlFor="time-from" className="text-sm font-semibold text-muted-foreground flex items-center gap-1.5" &gt; &lt;Clock className="size-3.5" /&gt; Start Time &lt;/Label&gt; &lt;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" /&gt; &lt;/div&gt; &lt;div className="grid gap-2"&gt; &lt;Label htmlFor="time-to" className="text-sm font-semibold text-muted-foreground flex items-center gap-1.5" &gt; &lt;Clock className="size-3.5" /&gt; End Time &lt;/Label&gt; &lt;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" /&gt; &lt;/div&gt; &lt;/div&gt; &lt;Button className="w-full h-11 font-semibold transition-all group overflow-hidden relative cursor-pointer" onClick={handleBooking} disabled={!date || bookingStatus !== "idle"} &gt; {bookingStatus === "idle" &amp;&amp; ( &lt;span className="flex items-center gap-2"&gt;Confirm Meet&lt;/span&gt; )} {bookingStatus === "loading" &amp;&amp; ( &lt;div className="flex items-center gap-2"&gt; &lt;div className="h-4 w-4 animate-spin rounded-full border-2 border-current border-t-transparent" /&gt; Processing... &lt;/div&gt; )} {bookingStatus === "success" &amp;&amp; ( &lt;span className="flex items-center gap-2 animate-in zoom-in-50 duration-300"&gt; &lt;Check className="h-4 w-4" /&gt; Meet Scheduled! &lt;/span&gt; )} &lt;/Button&gt; &lt;/div&gt; &lt;/&gt; ); }; export default DateAndTimePickerDemo; </code></pre> <h3 id="heading-how-this-component-works">How This Component Works</h3> <p>This component combines three pieces of state into a simple booking flow:</p> <ul> <li><p>The selected date</p> </li> <li><p>Start and end times</p> </li> <li><p>The booking status (idle, loading, success)</p> </li> </ul> <p>The date is stored using React state:</p> <pre><code class="language-javascript">const [date, setDate] = useState&lt;Date | undefined&gt;(undefined); </code></pre> <p>When a user selects a day from the calendar, the <code>onSelect</code> callback updates the state and closes the popover:</p> <pre><code class="language-javascript">onSelect={(d) =&gt; { setDate(d); setOpen(false); }} </code></pre> <p>The calendar itself lives inside a <code>Popover</code>, which keeps the UI compact. Clicking the trigger button opens the calendar panel:</p> <pre><code class="language-plaintext">&lt;Popover open={open} onOpenChange={setOpen}&gt; </code></pre> <p>The displayed date uses <code>date-fns</code> formatting:</p> <pre><code class="language-plaintext">format(date, "PPP") </code></pre> <p>This converts a JavaScript <code>Date</code> object into a readable format such as:</p> <pre><code class="language-plaintext">July 22, 2026 </code></pre> <p>The time fields use native HTML inputs:</p> <pre><code class="language-plaintext">&lt;Input type="time" /&gt; </code></pre> <p>Native time inputs provide built-in browser support, mobile pickers, keyboard accessibility, and locale-aware formatting without additional libraries.</p> <p>The booking button demonstrates how UI state can change during an async action:</p> <pre><code class="language-plaintext">"idle" → "loading" → "success" </code></pre> <p>In a real application, <code>handleBooking()</code> would typically call an API endpoint instead of using <code>setTimeout</code>.</p> <p>This pattern works well for:</p> <ul> <li><p>Meeting schedulers</p> </li> <li><p>Appointment systems</p> </li> <li><p>Interview booking tools</p> </li> <li><p>Event registration forms</p> </li> <li><p>SaaS scheduling workflows</p> </li> </ul> <h2 id="heading-how-to-build-a-date-range-picker"><strong>How to Build a Date Range Picker</strong></h2> <p>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.</p> <p><strong>Folder structure:</strong></p> <pre><code class="language-javascript">components └── shadcn-space └── date-picker └── date-picker-02.tsx </code></pre> <p>Install this Shadcn Date Range Picker with:</p> <pre><code class="language-javascript">npx shadcn@latest add @shadcn-space/date-picker-02 </code></pre> <p><strong>Component code:</strong></p> <pre><code class="language-javascript">"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 = () =&gt; { const [date, setDate] = React.useState&lt;DateRange | undefined&gt;({ from: new Date(), to: addDays(new Date(), 7), }); return ( &lt;div className="grid gap-3 max-w-sm mx-auto"&gt; &lt;Label htmlFor="date-range" className="text-sm font-medium px-1"&gt; Select Travel Dates &lt;/Label&gt; &lt;div className={cn("grid gap-2")}&gt; &lt;Popover&gt; &lt;PopoverTrigger render={ &lt;Button id="date-range" variant={"outline"} className={cn( "w-full justify-start text-left font-normal h-11 transition-all hover:bg-muted/50 focus:ring-2 focus:ring-primary/20 cursor-pointer", !date &amp;&amp; "text-muted-foreground", )} &gt; &lt;CalendarIcon className="mr-2 h-4 w-4 opacity-70" /&gt; {date?.from ? ( date.to ? ( &lt;&gt; {format(date.from, "LLL dd, y")} -{" "} {format(date.to, "LLL dd, y")} &lt;/&gt; ) : ( format(date.from, "LLL dd, y") ) ) : ( &lt;span&gt;Pick a range&lt;/span&gt; )} &lt;ChevronDown className="ml-auto h-4 w-4 opacity-50" /&gt; &lt;/Button&gt; } /&gt; &lt;PopoverContent className="w-auto p-0 border-muted/20 shadow-xl" align="start" &gt; &lt;Calendar mode="range" defaultMonth={date?.from} selected={date} onSelect={setDate} numberOfMonths={2} className="p-3" /&gt; &lt;/PopoverContent&gt; &lt;/Popover&gt; &lt;/div&gt; &lt;p className="text-xs text-muted-foreground px-1"&gt; {date?.from &amp;&amp; date?.to ? `Stay duration: ${Math.round((date.to.getTime() - date.from.getTime()) / (1000 * 60 * 60 * 24))} nights` : "Please select a valid date range."} &lt;/p&gt; &lt;/div&gt; ); }; export default DateRangePickerDemo; </code></pre> <h3 id="heading-how-the-date-range-picker-works">How the Date Range Picker Works</h3> <p>This version switches the Calendar component from single-date mode to range mode:</p> <pre><code class="language-javascript">mode="range" </code></pre> <p>Instead of storing one <code>Date</code>, the component stores a <code>DateRange</code> object:</p> <pre><code class="language-javascript">{ from: Date, to: Date } </code></pre> <p>This makes it easy to work with booking systems, hotel stays, travel forms, and rental applications.</p> <p>The component displays two calendar months:</p> <pre><code class="language-javascript">numberOfMonths={2} </code></pre> <p>Showing two months reduces navigation and improves the selection experience for longer stays.</p> <p>The stay duration is calculated directly from the selected dates:</p> <pre><code class="language-javascript">(date.to.getTime() - date.from.getTime()) </code></pre> <p>This avoids additional libraries and keeps the logic simple.</p> <p>Compared to the first example:</p> <ul> <li><p>Uses <code>DateRange</code> instead of a single <code>Date</code></p> </li> <li><p>Uses <code>mode="range"</code></p> </li> <li><p>Displays two months</p> </li> <li><p>Adds derived data like total nights</p> </li> </ul> <h2 id="heading-how-to-build-a-time-picker"><strong>How to Build a Time Picker</strong></h2> <p>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.</p> <p><strong>Folder structure:</strong></p> <pre><code class="language-javascript">components └── shadcn-space └── date-picker └── date-picker-03.tsx </code></pre> <p>Install this Shadcn Time Picker with:</p> <pre><code class="language-javascript">npx shadcn@latest add @shadcn-space/date-picker-03 </code></pre> <p><strong>Component code:</strong></p> <pre><code class="language-javascript">"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 = () =&gt; { const inputRef = useRef&lt;HTMLInputElement&gt;(null); const handleShowPicker = () =&gt; { if (inputRef.current &amp;&amp; "showPicker" in inputRef.current) { try { inputRef.current.showPicker(); } catch (error) { console.error("Failed to open native picker:", error); } } }; return ( &lt;div className="flex w-full max-w-xs flex-col gap-2"&gt; &lt;Label htmlFor="time-picker"&gt;Select Slot&lt;/Label&gt; &lt;InputGroup&gt; &lt;InputGroupAddon align="inline-start" className="cursor-pointer hover:text-foreground transition-colors" onClick={handleShowPicker} title="Open time picker" &gt; &lt;Clock8Icon className="size-4" /&gt; &lt;/InputGroupAddon&gt; &lt;InputGroupInput ref={inputRef} type="time" id="time-picker" step="1" defaultValue="08:30:00" className="appearance-none [&amp;::-webkit-calendar-picker-indicator]:hidden [&amp;::-webkit-calendar-picker-indicator]:appearance-none" /&gt; &lt;/InputGroup&gt; &lt;/div&gt; ); }; export default TimePickerWithIconDemo; </code></pre> <h3 id="heading-how-the-time-picker-works">How the Time Picker Works</h3> <p>This component uses the browser's native time picker instead of building a custom dropdown.</p> <p>The input is referenced with <code>useRef</code>:</p> <pre><code class="language-javascript">const inputRef = useRef&lt;HTMLInputElement&gt;(null); </code></pre> <p>This allows the icon button to access the input element directly.</p> <p>When users click the clock icon, the browser's picker opens programmatically:</p> <pre><code class="language-javascript">inputRef.current.showPicker(); </code></pre> <p>The <code>showPicker()</code> method is a modern browser API that opens the same interface users would see if they clicked the input manually.</p> <p>The component wraps the input inside an <code>InputGroup</code>:</p> <pre><code class="language-plaintext">&lt;InputGroup&gt; </code></pre> <p>This creates a cleaner layout where the icon behaves as part of the field rather than a separate button.</p> <p>The browser's default picker icon is hidden:</p> <pre><code class="language-plaintext">[&amp;::-webkit-calendar-picker-indicator]:hidden </code></pre> <p>This prevents duplicate icons and gives full control over the UI.</p> <p>The benefit of this approach is that it keeps:</p> <ul> <li><p>Native accessibility</p> </li> <li><p>Mobile keyboard support</p> </li> <li><p>Locale-aware formatting</p> </li> <li><p>Better browser compatibility</p> </li> </ul> <p>Instead of rebuilding time selection from scratch, the component improves the native experience with custom styling.</p> <h2 id="heading-live-preview-of-the-components"><strong>Live Preview of the Components</strong></h2> <img src="https://cdn.hashnode.com/uploads/covers/68b53a3d851476bd2ce87f12/f815455d-9f2d-4ad1-ac9c-155c37aae094.gif" alt="f815455d-9f2d-4ad1-ac9c-155c37aae094" style="display:block;margin:0 auto" width="1152" height="648" loading="lazy"> <h2 id="heading-key-concepts-recap"><strong>Key Concepts Recap</strong></h2> <ul> <li><p><strong>Pick the pattern that matches the data you're collecting</strong>: A single date and time for bookings, a range for stays, a bare time field for slots.</p> </li> <li><p><strong>Every component installs the same way</strong>: One CLI command per component: <code>pnpm</code>, <code>npm</code>, <code>yarn</code>, and <code>bun</code>. All are supported. Adding a new picker to a project takes one line, not a manual build.</p> </li> <li><p><strong>The date and time picker separates date state from time state</strong>, then combines them at the confirmation step. This keeps the calendar and the time inputs from getting out of sync.</p> </li> <li><p><strong>The range picker leans on the Calendar's built-in</strong> <code>mode="range"</code>: The two-month view and the night count come from the selection state, not custom range math.</p> </li> <li><p><strong>The time picker wraps the native</strong> <code>&lt;input type="time"&gt;</code> <strong>instead of replacing it</strong>: This keeps native accessibility and mobile keyboard behavior intact.</p> </li> </ul> <h2 id="heading-conclusion"><strong>Conclusion</strong></h2> <p>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.</p> <p>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.</p> <h2 id="heading-resources"><strong>Resources</strong></h2> <ul> <li><p><a href="https://ui.shadcn.com/docs"><strong>shadcn/ui documentation</strong></a>: For the official Popover, Calendar, and Button component APIs</p> </li> <li><p><a href="https://date-fns.org/docs/Getting-Started"><strong>date-fns documentation</strong></a>: For the date helper functions and locale files used in this tutorial</p> </li> <li><p><a href="https://daypicker.dev/"><strong>react-day-picker documentation</strong></a>: For the range and multi-date selection modes that power the shadcn/ui Calendar</p> </li> <li><p><a href="https://developer.mozilla.org/en-US/docs/Web/Accessibility/ARIA/ARIA_Live_Regions"><strong>MDN: ARIA live regions</strong></a>: For more on how <code>role="alert"</code> works</p> </li> <li><p><a href="https://shadcnspace.com/components/date-picker"><strong>Shadcn Date Time Picker components</strong></a>: If you want more prebuilt date and time picker variants to compare against.</p> </li> <li><p><a href="https://shadcnspace.com/components"><strong>Full Shadcn components library</strong></a>: For other shadcn/ui-compatible components</p> </li> <li><p><a href="https://wrappixel.com/blog/shadcn-date-picker"><strong>Guide to free Shadcn date and time pickers</strong></a></p> </li> </ul>

How to apply

  1. 1 Read the full details above and confirm you meet the eligibility criteria.
  2. 2 Prepare your documents — an updated CV, and any cover letter, proposal or certificates required.
  3. 3 Click Apply on official site to complete your application on Vaibhav Gupta’s official page.
  4. 4 Submit as early as possible — many close once filled.
Apply on official site

Sourced from freecodecamp. Always verify details on the official website. Opportunihub never charges you to apply.

Frequently asked questions

How do I apply for How to Build a Reusable Date-Time Picker in React with shadcn/ui?

Review the full details and eligibility on this page, prepare your documents, then use the “Apply on official site” button to complete your application on Vaibhav Gupta’s official page.

Is this opportunity remote or location-based?

This opportunity is remote-friendly and open to applicants who can work from anywhere.

Is How to Build a Reusable Date-Time Picker in React with shadcn/ui free to apply for?

Opportunihub lists this Course for free. Legitimate Courses do not ask for payment to apply — never pay a fee to submit an application.