Skip to content
Drivn logoDrivn
5 min read

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

FeatureDrivnshadcn/ui
Underlying libraryreact-day-pickerreact-day-picker
Component shapeSingle importBlock recipe (5 imports)
Trigger stylingBuilt-inCompose with Button + Popover
Range mode<DatePicker.Range />Duplicate recipe with mode="range"
Year dropdown shortcutfromYear / toYearManual startMonth / endMonth
Custom formatformatDate propformat() from date-fns inline
Outside-click + EscapeBuilt-inInherited from Popover
Locale supportreact-day-picker/localereact-day-picker/locale + date-fns/locale
LicenseMITMIT
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
2import { useState } from 'react'
3import { format } from 'date-fns'
4import { CalendarIcon } from 'lucide-react'
5import { Button } from '@/components/ui/button'
6import { Calendar } from '@/components/ui/calendar'
7import {
8 Popover,
9 PopoverContent,
10 PopoverTrigger,
11} from '@/components/ui/popover'
12import { cn } from '@/lib/utils'
13
14export 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
42import { useState } from 'react'
43import { DatePicker } from '@/components/ui/date-picker'
44
45export 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.

1import { useState } from 'react'
2import {
3 DatePicker,
4 type DateRange,
5} from '@/components/ui/date-picker'
6
7export 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.

1import { useState } from 'react'
2import { DatePicker } from '@/components/ui/date-picker'
3import { cs } from 'react-day-picker/locale'
4
5export 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}
Get started

Install Drivn in one command

Copy the source into your project and own every line. Zero runtime dependencies, pure React + Tailwind.

npx drivn@latest create

Requires Node 18+. Works with npm, pnpm, and yarn.

Enjoying Drivn?
Star the repo on GitHub to follow new component releases.
Star →

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.