Drivn vs shadcn/ui — Date Picker Component Compared
Side-by-side comparison of Drivn DatePicker vs the shadcn/ui Popover and Calendar recipe — single import, range mode, format prop, and locale wiring.
Drivn and shadcn/ui take different approaches to the date picker. shadcn/ui does not ship a DatePicker component at all — its docs publish a date picker as a block recipe that wires together Popover, Button, Calendar, and a date-fns format call inside your application file. You copy roughly fifty lines of composition into every form that needs a date input. Drivn ships DatePicker as a single component you import like any other, with the trigger button, popover behavior, outside-click handling, and Escape dismissal already built in.
The difference is most visible in what you write per form. With shadcn you assemble the trigger from a Button, manage open state, format the selected date inline with date-fns, and inject the Calendar inside a PopoverContent. With Drivn you write <DatePicker selected={date} onSelect={setDate} /> and the wrapped composition is internal. Both render the same calendar grid because Drivn's Calendar uses the same react-day-picker underneath as shadcn's, and both expose locale, custom formatting, and the matcher API.
This page lays out every difference: API shape, range mode, custom formatting, year dropdown selection, and locale wiring. The shadcn equivalent shown in each section is the published block recipe, not a hypothetical wrapper. If you already use shadcn's Calendar in a form and reach for the popover input pattern at every call site, the Drivn DatePicker collapses that pattern into one prop-driven import.
Side-by-side comparison
| Feature | Drivn | shadcn/ui |
|---|---|---|
| Underlying library | react-day-picker | react-day-picker |
| Component shape | Single import | Block recipe (5 imports) |
| Trigger styling | Built-in | Compose with Button + Popover |
| Range mode | <DatePicker.Range /> | Duplicate recipe with mode="range" |
| Year dropdown shortcut | fromYear / toYear | Manual startMonth / endMonth |
| Custom format | formatDate prop | format() from date-fns inline |
| Outside-click + Escape | Built-in | Inherited from Popover |
| Locale support | react-day-picker/locale | react-day-picker/locale + date-fns/locale |
| License | MIT | MIT |
| Copy-paste install |
API side-by-side
shadcn/ui's date picker is a block recipe — copy these five components into your form file and wire them yourself. The Popover handles open state, the Button is the trigger, the Calendar lives inside PopoverContent, and you call format(date, 'PPP') from date-fns to render the selected day inside the button. Drivn collapses that into a single DatePicker import. The trigger is a plain <button> styled to match the Input component, with a CalendarDays icon on the left and the formatted date in the middle. Open state lives inside the component; outside-click and Escape dismiss the popover automatically.
Both approaches render the same calendar grid because they both wrap react-day-picker. The difference is what you write per form — with Drivn the call site stays one element and the format function is a prop, not an inline import.
1 // shadcn/ui — block recipe 2 import { useState } from 'react' 3 import { format } from 'date-fns' 4 import { CalendarIcon } from 'lucide-react' 5 import { Button } from '@/components/ui/button' 6 import { Calendar } from '@/components/ui/calendar' 7 import { 8 Popover, 9 PopoverContent, 10 PopoverTrigger, 11 } from '@/components/ui/popover' 12 import { cn } from '@/lib/utils' 13 14 export function DatePicker() { 15 const [date, setDate] = useState<Date>() 16 return ( 17 <Popover> 18 <PopoverTrigger asChild> 19 <Button 20 variant="outline" 21 className={cn( 22 'w-[240px] justify-start text-left font-normal', 23 !date && 'text-muted-foreground' 24 )} 25 > 26 <CalendarIcon /> 27 {date ? format(date, 'PPP') : <span>Pick a date</span>} 28 </Button> 29 </PopoverTrigger> 30 <PopoverContent className="w-auto p-0" align="start"> 31 <Calendar 32 mode="single" 33 selected={date} 34 onSelect={setDate} 35 /> 36 </PopoverContent> 37 </Popover> 38 ) 39 } 40 41 // Drivn — single import, trigger built-in 42 import { useState } from 'react' 43 import { DatePicker } from '@/components/ui/date-picker' 44 45 export default function Page() { 46 const [date, setDate] = useState<Date | undefined>() 47 return ( 48 <DatePicker selected={date} onSelect={setDate} /> 49 ) 50 }
Range selection with DatePicker.Range
The range variant mounts on the same DatePicker import as DatePicker.Range. The selected prop narrows to DateRange | undefined from react-day-picker, and the popover stays open until both from and to are set — clicking the same day twice does not collapse the range. The trigger label formats as Apr 1 – Apr 7 using the shared formatter, with the en dash inserted by formatRangeLabel inside the component.
shadcn's published recipe for range selection is a separate block file that copies the single-date recipe and changes mode from single to range. You end up with two near-identical files in the components directory — date-picker.tsx and date-picker-range.tsx — each duplicating the same Popover-Button-Calendar plumbing. Drivn keeps both behind a single dot-notation API, and the Calendar source the two share lives once in your repo.
1 import { useState } from 'react' 2 import { 3 DatePicker, 4 type DateRange, 5 } from '@/components/ui/date-picker' 6 7 export default function Page() { 8 const [range, setRange] = useState<DateRange | undefined>() 9 10 return ( 11 <DatePicker.Range 12 selected={range} 13 onSelect={setRange} 14 /> 15 ) 16 }
Year dropdown and custom formatting
Drivn's DatePicker forwards a variant prop to the wrapped Calendar — default, weekNumbers, or dropdown. With variant="dropdown", the calendar caption switches to month and year <select> elements bounded by fromYear and toYear. Pass variant="dropdown" fromYear={1950} toYear={2030} and the dropdown lists eighty years without you constructing Date objects.
shadcn's recipe does not surface this directly — to switch its Calendar to dropdown captions you edit the recipe file, pass captionLayout="dropdown" plus startMonth={new Date(1950, 0)} and endMonth={new Date(2030, 11)} props through the block, and update the trigger to fit the wider caption row. For custom formatting, Drivn takes a formatDate function prop. shadcn relies on you importing format from date-fns and calling it inside the trigger Button. Same output, different surface — see the Date Picker docs for every prop.
1 // Drivn — variant prop + formatDate prop 2 <DatePicker 3 variant="dropdown" 4 fromYear={1950} 5 toYear={2030} 6 selected={date} 7 onSelect={setDate} 8 formatDate={(d) => 9 d.toLocaleDateString('en-GB', { 10 day: '2-digit', 11 month: '2-digit', 12 year: 'numeric', 13 }) 14 } 15 /> 16 17 // shadcn — edit the recipe to pass captionLayout + format inline 18 <Calendar 19 mode="single" 20 captionLayout="dropdown" 21 startMonth={new Date(1950, 0)} 22 endMonth={new Date(2030, 11)} 23 selected={date} 24 onSelect={setDate} 25 /> 26 {date ? format(date, 'dd/MM/yyyy') : 'Pick a date'}
Locale and internationalization
Both libraries pass locale objects from react-day-picker/locale straight through to the underlying DayPicker. Drivn forwards the locale prop through DatePicker into Calendar so the trigger label, weekday headers, month names, and first-day-of-week all update in one prop. Import cs from react-day-picker/locale and pass it in — no provider needed.
shadcn's recipe accepts the locale on the inner <Calendar locale={...} /> only — the trigger Button label still calls format(date, 'PPP') from date-fns, so localizing the trigger means swapping date-fns to format(date, 'PPP', { locale: cs }) from date-fns/locale. Drivn passes one prop and the trigger label, calendar caption, and weekday row update together, because the same formatDate and locale props feed both surfaces. The Calendar comparison covers every other locale trade-off they share.
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 8 return ( 9 <DatePicker 10 locale={cs} 11 selected={date} 12 onSelect={setDate} 13 /> 14 ) 15 }
Install Drivn in one command
Copy the source into your project and own every line. Zero runtime dependencies, pure React + Tailwind.
npx drivn@latest createRequires Node 18+. Works with npm, pnpm, and yarn.
Frequently asked questions
Not as a component — shadcn publishes a date picker as a block recipe in its documentation. You copy a roughly fifty-line file that imports Popover, Button, Calendar, and date-fns, and you paste it into your project as components/date-picker.tsx. Drivn ships the same composition as a built-in component you install with npx drivn add date-picker, so the call site stays one import.
Use DatePicker.Range. It mounts on the same import as the default DatePicker via dot notation, accepts a DateRange | undefined selected prop, and reports back when the user picks both from and to dates. The popover stays open between the two clicks and dismisses automatically once the range completes — no second component to install or import.
Yes — pass placeholder, formatDate, and className for the most common changes. For larger structural edits, the trigger lives in your components/ui/date-picker.tsx file after install, so any styling change is a local edit. The styles.trigger cn() call defines border, padding, and focus styling — change one line and rebuild.
In Drivn, no — pass fromYear and toYear as plain numbers and the component converts them into the startMonth and endMonth Date objects react-day-picker needs. The defaults are today's year plus and minus twenty, suitable for typical near-future scheduling. shadcn's recipe leaves both as Date props you assemble at every call site.
Part of the DatePicker. The component manages its own open state with useState(false), listens for outside clicks via a mousedown listener on the document, and dismisses on Escape via a keyboard listener. There is no Popover primitive to install. If you already use the Drivn Popover elsewhere, the DatePicker is independent of it.