Shadcn Date Picker
Free shadcn/ui date picker for React — a reusable
Installation
{
"registries": {
"@designrevision": "https://registry.designrevision.com/r/{name}.json"
}
}
Add to your existing components.json. Requires shadcn/ui v2.3+.
Packages
Props
value
= —
Date | undefined
The selected date (controlled).
onValueChange
= —
(date: Date | undefined) => void
Fires when a date is picked; the popover closes on select.
placeholder
= "Pick a date"
string
Trigger text shown when no date is selected.
disabled
= false
boolean
Disables the trigger.
A reusable single-date picker wrapping Calendar + Popover + Button — a formatted outline trigger (date-fns format(date, 'PPP')) that opens the calendar and closes on select. For date ranges, date-of-birth dropdowns, a typed input synced to the calendar, presets, or disabled dates, compose Calendar + Popover directly (see the examples).
"use client"
import * as React from "react"
import { format } from "date-fns"
import { CalendarIcon } from "lucide-react"
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"
export interface DatePickerProps {
value?: Date
onValueChange?: (date: Date | undefined) => void
placeholder?: string
className?: string
disabled?: boolean
id?: string
}
function DatePicker({
value,
onValueChange,
placeholder = "Pick a date",
className,
disabled,
id,
}: DatePickerProps) {
const [open, setOpen] = React.useState(false)
return (
<Popover open={open} onOpenChange={setOpen}>
<PopoverTrigger asChild>
<Button
id={id}
variant="outline"
disabled={disabled}
data-empty={!value}
className={cn(
"data-[empty=true]:text-muted-foreground w-[240px] justify-start text-left font-normal",
className
)}
>
<CalendarIcon />
{value ? format(value, "PPP") : <span>{placeholder}</span>}
</Button>
</PopoverTrigger>
<PopoverContent className="w-auto p-0" align="start">
<Calendar
mode="single"
selected={value}
onSelect={(date) => {
onValueChange?.(date)
setOpen(false)
}}
autoFocus
/>
</PopoverContent>
</Popover>
)
}
export { DatePicker }
Examples
Date Picker
The drop-in `
Packages
Props
No props documented yet.
"use client"
import * as React from "react"
import { DatePicker } from "@/components/ui/date-picker"
export default function DatePickerDemo01() {
const [date, setDate] = React.useState<Date>()
return (
<DatePicker value={date} onValueChange={setDate} placeholder="Pick a date" />
)
}
Raw composition
The canonical shadcn pattern — [Popover](/components/popover) + Button + [Calendar](/components/calendar) composed by hand with date-fns formatting.
Packages
Props
No props documented yet.
"use client"
import * as React from "react"
import { format } from "date-fns"
import { CalendarIcon } from "lucide-react"
import { Button } from "@/components/ui/button"
import { Calendar } from "@/components/ui/calendar"
import {
Popover,
PopoverContent,
PopoverTrigger,
} from "@/components/ui/popover"
export default function DatePickerComposition01() {
const [open, setOpen] = React.useState(false)
const [date, setDate] = React.useState<Date>()
return (
<Popover open={open} onOpenChange={setOpen}>
<PopoverTrigger asChild>
<Button
variant="outline"
data-empty={!date}
className="data-[empty=true]:text-muted-foreground w-[240px] justify-start text-left font-normal"
>
<CalendarIcon />
{date ? format(date, "PPP") : <span>Pick a date</span>}
</Button>
</PopoverTrigger>
<PopoverContent className="w-auto p-0" align="start">
<Calendar
mode="single"
selected={date}
onSelect={(value) => {
setDate(value)
setOpen(false)
}}
autoFocus
/>
</PopoverContent>
</Popover>
)
}
Date range
A two-month range picker with a "Jan 01 – Jan 08" trigger — `mode="range"` in a popover.
Packages
Props
No props documented yet.
"use client"
import * as React from "react"
import { format } from "date-fns"
import { CalendarIcon } from "lucide-react"
import { type DateRange } from "react-day-picker"
import { Button } from "@/components/ui/button"
import { Calendar } from "@/components/ui/calendar"
import {
Popover,
PopoverContent,
PopoverTrigger,
} from "@/components/ui/popover"
export default function DatePickerRange01() {
const [range, setRange] = React.useState<DateRange | undefined>()
return (
<Popover>
<PopoverTrigger asChild>
<Button
variant="outline"
data-empty={!range?.from}
className="data-[empty=true]:text-muted-foreground w-[300px] justify-start text-left font-normal"
>
<CalendarIcon />
{range?.from ? (
range.to ? (
<>
{format(range.from, "LLL dd, y")} – {format(range.to, "LLL dd, y")}
</>
) : (
format(range.from, "LLL dd, y")
)
) : (
<span>Pick a date range</span>
)}
</Button>
</PopoverTrigger>
<PopoverContent className="w-auto p-0" align="start">
<Calendar
mode="range"
selected={range}
onSelect={setRange}
numberOfMonths={2}
defaultMonth={range?.from}
autoFocus
/>
</PopoverContent>
</Popover>
)
}
Date of birth
A DOB picker with month/year dropdowns (`captionLayout="dropdown"`) bounded from 1925 to today.
Packages
Props
No props documented yet.
"use client"
import * as React from "react"
import { format } from "date-fns"
import { CalendarIcon } from "lucide-react"
import { Button } from "@/components/ui/button"
import { Calendar } from "@/components/ui/calendar"
import { Label } from "@/components/ui/label"
import {
Popover,
PopoverContent,
PopoverTrigger,
} from "@/components/ui/popover"
export default function DatePickerDob01() {
const [open, setOpen] = React.useState(false)
const [date, setDate] = React.useState<Date>()
return (
<div className="grid gap-2">
<Label htmlFor="dob">Date of birth</Label>
<Popover open={open} onOpenChange={setOpen}>
<PopoverTrigger asChild>
<Button
id="dob"
variant="outline"
data-empty={!date}
className="data-[empty=true]:text-muted-foreground w-[240px] justify-start text-left font-normal"
>
<CalendarIcon />
{date ? format(date, "PPP") : <span>Select your date of birth</span>}
</Button>
</PopoverTrigger>
<PopoverContent className="w-auto p-0" align="start">
<Calendar
mode="single"
selected={date}
onSelect={(value) => {
setDate(value)
setOpen(false)
}}
captionLayout="dropdown"
startMonth={new Date(1925, 0)}
endMonth={new Date()}
disabled={{ after: new Date() }}
defaultMonth={date ?? new Date(1995, 0)}
autoFocus
/>
</PopoverContent>
</Popover>
</div>
)
}
Input + calendar
A **typed** date input synced to the calendar — parse the text to a `Date`, jump the month, and Arrow-Down opens the popover.
Packages
Props
No props documented yet.
"use client"
import * as React from "react"
import { format, isValid, parse } from "date-fns"
import { CalendarIcon } from "lucide-react"
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"
export default function DatePickerInput01() {
const [open, setOpen] = React.useState(false)
const [date, setDate] = React.useState<Date | undefined>(new Date())
const [month, setMonth] = React.useState<Date | undefined>(new Date())
const [value, setValue] = React.useState(format(new Date(), "MMMM d, yyyy"))
return (
<div className="grid gap-2">
<Label htmlFor="date-input">Schedule date</Label>
<div className="relative flex gap-2">
<Input
id="date-input"
value={value}
placeholder="June 1, 2025"
className="bg-background pr-10"
onChange={(event) => {
setValue(event.target.value)
const parsed = parse(event.target.value, "MMMM d, yyyy", new Date())
if (isValid(parsed)) {
setDate(parsed)
setMonth(parsed)
}
}}
onKeyDown={(event) => {
if (event.key === "ArrowDown") {
event.preventDefault()
setOpen(true)
}
}}
/>
<Popover open={open} onOpenChange={setOpen}>
<PopoverTrigger asChild>
<Button
variant="ghost"
className="absolute top-1/2 right-1 size-7 -translate-y-1/2"
aria-label="Open calendar"
>
<CalendarIcon className="size-3.5" />
</Button>
</PopoverTrigger>
<PopoverContent className="w-auto p-0" align="end">
<Calendar
mode="single"
selected={date}
month={month}
onMonthChange={setMonth}
onSelect={(selected) => {
setDate(selected)
if (selected) setValue(format(selected, "MMMM d, yyyy"))
setOpen(false)
}}
autoFocus
/>
</PopoverContent>
</Popover>
</div>
</div>
)
}
In a form
A labelled date picker inside a form that reports the formatted date on submit.
Packages
Props
No props documented yet.
"use client"
import * as React from "react"
import { format } from "date-fns"
import { CalendarIcon } from "lucide-react"
import { Button } from "@/components/ui/button"
import { Calendar } from "@/components/ui/calendar"
import { Label } from "@/components/ui/label"
import {
Popover,
PopoverContent,
PopoverTrigger,
} from "@/components/ui/popover"
export default function DatePickerForm01() {
const [open, setOpen] = React.useState(false)
const [date, setDate] = React.useState<Date>()
const [submitted, setSubmitted] = React.useState<string | null>(null)
return (
<form
onSubmit={(event) => {
event.preventDefault()
setSubmitted(date ? format(date, "PPP") : "nothing")
}}
className="grid gap-3"
>
<div className="grid gap-2">
<Label htmlFor="event-date">Event date</Label>
<Popover open={open} onOpenChange={setOpen}>
<PopoverTrigger asChild>
<Button
id="event-date"
variant="outline"
data-empty={!date}
className="data-[empty=true]:text-muted-foreground w-[240px] justify-start text-left font-normal"
>
<CalendarIcon />
{date ? format(date, "PPP") : <span>Pick a date</span>}
</Button>
</PopoverTrigger>
<PopoverContent className="w-auto p-0" align="start">
<Calendar
mode="single"
selected={date}
onSelect={(value) => {
setDate(value)
setOpen(false)
}}
autoFocus
/>
</PopoverContent>
</Popover>
<p className="text-muted-foreground text-sm">
Your event will be scheduled for this date.
</p>
</div>
<Button type="submit" className="w-fit">
Save
</Button>
{submitted && (
<p className="text-sm">
Saved: <span className="font-medium">{submitted}</span>
</p>
)}
</form>
)
}
With presets
Quick-select buttons (Today / Tomorrow / In a week) above the calendar inside the popover.
Packages
Props
No props documented yet.
"use client"
import * as React from "react"
import { addDays, format } from "date-fns"
import { CalendarIcon } from "lucide-react"
import { Button } from "@/components/ui/button"
import { Calendar } from "@/components/ui/calendar"
import {
Popover,
PopoverContent,
PopoverTrigger,
} from "@/components/ui/popover"
const presets = [
{ label: "Today", days: 0 },
{ label: "Tomorrow", days: 1 },
{ label: "In 3 days", days: 3 },
{ label: "In a week", days: 7 },
]
export default function DatePickerPresets01() {
const [open, setOpen] = React.useState(false)
const [date, setDate] = React.useState<Date>()
const [month, setMonth] = React.useState<Date>(new Date())
const pick = (days: number) => {
const next = addDays(new Date(), days)
setDate(next)
setMonth(next)
setOpen(false)
}
return (
<Popover open={open} onOpenChange={setOpen}>
<PopoverTrigger asChild>
<Button
variant="outline"
data-empty={!date}
className="data-[empty=true]:text-muted-foreground w-[240px] justify-start text-left font-normal"
>
<CalendarIcon />
{date ? format(date, "PPP") : <span>Pick a date</span>}
</Button>
</PopoverTrigger>
<PopoverContent className="flex w-auto flex-col p-0" align="start">
<div className="flex flex-col gap-1 border-b p-2">
{presets.map((preset) => (
<Button
key={preset.label}
variant="ghost"
size="sm"
className="justify-start font-normal"
onClick={() => pick(preset.days)}
>
{preset.label}
</Button>
))}
</div>
<Calendar
mode="single"
selected={date}
month={month}
onMonthChange={setMonth}
onSelect={(value) => {
setDate(value)
setOpen(false)
}}
autoFocus
/>
</PopoverContent>
</Popover>
)
}
Disabled dates
A check-in picker that blocks past dates and weekends with the `disabled` matcher.
Packages
Props
No props documented yet.
"use client"
import * as React from "react"
import { format } from "date-fns"
import { CalendarIcon } from "lucide-react"
import { Button } from "@/components/ui/button"
import { Calendar } from "@/components/ui/calendar"
import { Label } from "@/components/ui/label"
import {
Popover,
PopoverContent,
PopoverTrigger,
} from "@/components/ui/popover"
export default function DatePickerDisabled01() {
const [open, setOpen] = React.useState(false)
const [date, setDate] = React.useState<Date>()
return (
<div className="grid gap-2">
<Label htmlFor="checkin">Check-in date</Label>
<Popover open={open} onOpenChange={setOpen}>
<PopoverTrigger asChild>
<Button
id="checkin"
variant="outline"
data-empty={!date}
className="data-[empty=true]:text-muted-foreground w-[240px] justify-start text-left font-normal"
>
<CalendarIcon />
{date ? format(date, "PPP") : <span>Select a date</span>}
</Button>
</PopoverTrigger>
<PopoverContent className="w-auto p-0" align="start">
<Calendar
mode="single"
selected={date}
onSelect={(value) => {
setDate(value)
setOpen(false)
}}
// No past dates and no weekends.
disabled={[{ before: new Date() }, { dayOfWeek: [0, 6] }]}
autoFocus
/>
</PopoverContent>
</Popover>
</div>
)
}
The Shadcn Date Picker is a date field that opens a calendar — the Calendar inside a Popover behind a formatted button. Officially shadcn ships it as a composition pattern rather than a component; we give you both — a drop-in <DatePicker> for the common case and the raw composition for everything else: ranges, date-of-birth dropdowns, typed input, presets, and disabled dates.
Component or composition?
There's no canonical DatePicker primitive in shadcn — it's Calendar + Popover + Button wired together. That's flexible but repetitive, so we provide a small reusable <DatePicker> that covers the single-date 80% case:
<DatePicker value={date} onValueChange={setDate} placeholder="Pick a date" />
It renders a Button variant="outline" showing format(date, "PPP") (or the placeholder), opens the calendar in a w-auto p-0 popover, and closes on select. Reach for the raw composition (shown in every example below the first) when you need a range, a typed input, presets, or custom formatting.
Calendar vs Date Picker
- Calendar — the always-visible grid. Use it inline (a booking page, a dashboard widget).
- Date Picker — the Calendar in a Popover behind a field. Use it in forms where space is tight and the calendar should open on demand.
This is the same split as Table vs Data Table: one is the primitive, the other composes it.
Single date, range & date of birth
The basic picker is single-date. For a range, switch the Calendar to mode="range", hold a DateRange in state, render numberOfMonths={2}, and format the trigger as from – to (the Date range example). For a date of birth, set captionLayout="dropdown" with startMonth / endMonth bounds so the year is a dropdown — paging from 1995 to 1925 one month at a time is misery (the Date of birth example).
Typed input
Power users want to type. Pair an Input with a small calendar-icon trigger: parse the text with date-fns parse(...), and when it's isValid, set the date and jump the calendar's month. Arrow-Down opens the popover; selecting a day writes the formatted value back into the input. The Input + calendar example is the modern shadcn pattern.
Presets, forms & disabled dates
Add preset buttons (Today, Tomorrow, In a week) beside the calendar inside the popover for one-click common dates (the With presets example). Drop the picker into a form with a label and read the value on submit (the In a form example). Constrain selectable days with the disabled matcher — { before: new Date() } for future-only check-in dates, plus { dayOfWeek: [0, 6] } to block weekends (the Disabled dates example).
Formatting
All formatting is date-fns format: "PPP" → January 1, 2025, "LLL dd, y" → Jan 01, 2025, "MMMM d, yyyy" → January 1, 2025. Show a muted placeholder when empty with data-empty={!date} + data-[empty=true]:text-muted-foreground on the trigger.
Accessibility & theming
The trigger is a real Button, the popover manages focus (moving into the calendar on open and back to the trigger on close), and the Calendar brings react-day-picker's full keyboard navigation. Everything reads your design tokens — --popover, --accent, --primary — so it follows your theme, including dark mode, with no overrides.