How to Add a Date Picker to a React App
Step-by-step guide to adding a copy-and-own Date Picker to any React project with the Drivn CLI — single date, range, custom format, and locale support.
A date picker is a calendar tucked inside a popover — a compact form field that opens a month grid when clicked and closes once a date is chosen. Building one well means composing a trigger button, a floating panel, outside-click and Escape dismissal, and a formatted label, then wiring all of it to selection state. Most teams pull in a dated dependency to avoid the work.
Drivn writes the component into your repository instead. The CLI copies a date-picker.tsx file that composes Drivn's own Calendar inside a popover the component manages itself — no Radix, no floating-ui, just React state and a mousedown listener. After install the file is yours: read it end to end, restyle the styles object, and ship today. Because it relies on the Calendar's react-day-picker hooks it is a client component, marked 'use client', while the page around it can still render on the server.
This guide adds the Date Picker to an existing React app in about ten minutes — install the CLI, render it with controlled state, switch to range mode, format the trigger label, and localize the grid. It works the same in Vite + React or a Next.js App Router project. For the Next.js-specific walkthrough see the Next.js Date Picker guide; for live patterns see the Date Picker examples.
Prerequisites
Before installing the Date Picker, confirm your React project has the three things Drivn assumes: Tailwind CSS v4 installed and processing your CSS, TypeScript configured (the component ships as a .tsx file), and a @/ path alias pointing at your source directory. If you scaffolded with create-next-app, npm create vite, or npx drivn@latest create, all three are already wired. For a custom setup, check the compilerOptions.paths entry in tsconfig.json; the installation page lists the minimal config. The Date Picker composes the Calendar, which pulls in react-day-picker for the grid logic and lucide-react for the icons — the CLI resolves that dependency and adds both packages during install if they are missing.
Step 1 — Install Drivn via the CLI
Run the CLI from your project root to add the Date Picker source file. The command prompts once for your install directory (defaulting to src/components/ui/), writes date-picker.tsx, and resolves its component dependency by also writing calendar.tsx, since the Date Picker imports Calendar directly. It adds react-day-picker and lucide-react to your package.json if they are not already present. No global config file is created — both files are TypeScript you edit like any other component. Confirm they landed in your editor, then commit them. If your project uses a monorepo layout or a non-standard path, the CLI docs cover the flags for targeting a custom location during install.
1 # add the Date Picker (the CLI also writes its Calendar dependency) 2 npx drivn add date-picker 3 4 # verify both files were written 5 ls src/components/ui/date-picker.tsx src/components/ui/calendar.tsx
Step 2 — Import and render with controlled state
Open the page where the Date Picker should live and import it from your UI directory. The default render is a trigger button with a calendar icon and a placeholder; clicking it opens the month grid in a floating panel. Because the Date Picker holds its open state with useState and reads selection through the Calendar's react-day-picker hooks, the source begins with 'use client' — so hold the selected date in useState and bind selected and onSelect. In a Next.js App Router project, keep this in a small client component and render it as an island; in a Vite app it works anywhere. See the Date Picker docs for the full prop table.
1 'use client' 2 import { useState } from 'react' 3 import { DatePicker } from '@/components/ui/date-picker' 4 5 export function BookingDate() { 6 const [date, setDate] = useState<Date | undefined>() 7 return ( 8 <DatePicker 9 selected={date} 10 onSelect={setDate} 11 /> 12 ) 13 }
Step 3 — Range mode and a custom label format
Single-date selection is the default; two common variations are one prop away. For check-in/check-out flows use the DatePicker.Range sub-component, exposed through the same import via Object.assign in the source — it holds a { from, to } value, closes the panel once both endpoints are chosen, and you import the DateRange type to type your state. To change how the selected date reads in the trigger, pass a formatDate callback; without it the component formats with toLocaleDateString using { month: 'short', day: 'numeric', year: 'numeric' }. The Calendar docs cover the dropdown and week-number variants you can pass through the variant prop.
1 import { useState } from 'react' 2 import { 3 DatePicker, 4 type DateRange, 5 } from '@/components/ui/date-picker' 6 7 // range selection 8 <DatePicker.Range selected={range} onSelect={setRange} /> 9 10 // custom trigger label (DD/MM/YYYY) 11 <DatePicker 12 selected={date} 13 onSelect={setDate} 14 formatDate={(d) => 15 d.toLocaleDateString('en-GB', { 16 day: '2-digit', 17 month: '2-digit', 18 year: 'numeric', 19 }) 20 } 21 />
Step 4 — Localize and customize the styles
The Date Picker forwards a locale prop down to the Calendar, so localizing the month grid is a one-line change: import a locale object from react-day-picker/locale — cs for Czech, de for German — and pass it to the component. The default label formatter also reads locale.code, so the trigger text follows the same locale. To restyle the surface, open src/components/ui/date-picker.tsx and edit the styles object near the top — trigger controls the button (border border-input rounded-[10px]), and content positions the panel with absolute top-full left-0 mt-1 z-50 and animates it with transition-[opacity,scale]. Every class reads from your Tailwind tokens, so editing the colors in globals re-themes every Date Picker at once.
1 import { useState } from 'react' 2 import { DatePicker } from '@/components/ui/date-picker' 3 import { cs } from 'react-day-picker/locale' 4 5 export default function Page() { 6 const [date, setDate] = useState<Date | undefined>() 7 return ( 8 <DatePicker 9 locale={cs} 10 selected={date} 11 onSelect={setDate} 12 /> 13 ) 14 }
Install Drivn in one command
Copy the source into your project and own every line. Zero runtime dependencies, pure React + Tailwind.
Frequently asked questions
Yes. Set the project up with TypeScript and Tailwind v4 first, then run npx drivn add date-picker. The Date Picker has no dependency on Next.js or any router — it renders anywhere React and Tailwind reach the DOM. Vite + React is the most common non-Next setup and works without extra configuration; the CLI also writes the Calendar dependency the Date Picker composes.
The Date Picker is a client component — it is marked 'use client' because it holds its open state with useState and reads selection through the Calendar's react-day-picker hooks. Your surrounding page and layout still render on the server. The usual pattern is a small client component holding the picker and its state, rendered as an island inside an otherwise server-rendered route. Next.js inserts the client boundary at the import automatically.
Use the DatePicker.Range sub-component exposed through the same import. It renders the Calendar in range mode and expects a selected value shaped as { from, to }. Import the DateRange type alongside the component to type your state. The panel closes automatically once both endpoints are chosen, and the trigger shows a formatted "from – to" label built from your formatDate callback or the default formatter.
The Date Picker source attaches a mousedown listener to document inside a useEffect that runs while the panel is open. When the click target is not contained by the component ref, it sets open to false. A second effect listens for the Escape key and closes the panel the same way. Both are about ten lines of plain React in the file you own — no Radix Popover and no floating-ui dependency.
Yes. Pass a formatDate callback that takes a Date and returns a string — for example (d) => d.toLocaleDateString('en-GB', { day: '2-digit', month: '2-digit', year: 'numeric' }) for a DD/MM/YYYY label. Without it, the component formats with toLocaleDateString using short month, numeric day, and numeric year, reading the locale code from any locale prop you pass.

