Free shadcn/ui date picker for React — a reusable built from Calendar + Popover. Single date, date range, date of birth, a typed input synced to the calendar, presets, and disabled dates. The examples official shadcn never shipped.

Installation

1 Configure registry — once per project
{
  "registries": {
    "@designrevision": "https://registry.designrevision.com/r/{name}.json"
  }
}

Add to your existing components.json. Requires shadcn/ui v2.3+.

2 Install the component

Packages

date-fns
lucide-react
utils
popover
button
calendar

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).

Copied!
components/ui/date-picker.tsx
"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 `` — a formatted outline trigger that opens the [Calendar](/components/calendar) and closes on select.

Packages

@designrevision/date-picker

Props

No props documented yet.

Copied!
components/ui/date-picker-demo-01.tsx
"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

date-fns
lucide-react
popover
button
calendar

Props

No props documented yet.

Copied!
components/ui/date-picker-composition-01.tsx
"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

date-fns
lucide-react
popover
button
calendar

Props

No props documented yet.

Copied!
components/ui/date-picker-range-01.tsx
"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

date-fns
lucide-react
popover
button
calendar
label

Props

No props documented yet.

Copied!
components/ui/date-picker-dob-01.tsx
"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

date-fns
lucide-react
popover
button
calendar
input
label

Props

No props documented yet.

Copied!
components/ui/date-picker-input-01.tsx
"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

date-fns
lucide-react
popover
button
calendar
label

Props

No props documented yet.

Copied!
components/ui/date-picker-form-01.tsx
"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

date-fns
lucide-react
popover
button
calendar

Props

No props documented yet.

Copied!
components/ui/date-picker-presets-01.tsx
"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

date-fns
lucide-react
popover
button
calendar
label

Props

No props documented yet.

Copied!
components/ui/date-picker-disabled-01.tsx
"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>
  )
}

Frequently asked questions

Officially the Date Picker is a composition pattern, not a primitive — you build it from Calendar + Popover. We ship both: a drop-in for the common single-date case (the Installation snippet above) and the raw Popover + Button + Calendar composition (the Raw composition example) for ranges, typed input, presets, and disabled dates.
The [Calendar](/components/calendar) is the always-visible date grid. A Date Picker is that Calendar inside a [Popover](/components/popover) behind a button, so the field stays compact and the calendar opens on click. This page is the Date Picker; the standalone grid is the Calendar.
Use mode="range" on the Calendar, hold a DateRange ({ from, to }) in state, and format the trigger as "from – to" with date-fns (e.g. format(range.from, "LLL dd, y")). Render numberOfMonths={2} so the user can drag a range across two months. The Date range example is the full pattern.
Set captionLayout="dropdown" on the Calendar and bound it with startMonth / endMonth (e.g. new Date(1925, 0) to new Date()), so the month and year become dropdowns instead of paging year by year. Disable the future with disabled={{ after: new Date() }}. The Date of birth example shows it.
Render an Input next to a calendar-icon Popover trigger. On change, parse the text with date-fns parse(value, "MMMM d, yyyy", new Date()); if it isValid, set the date and jump the calendar month. Open the popover on Arrow-Down, and write the formatted value back into the input on select. The Input + calendar example implements this.
Control the Popover open state and call setOpen(false) inside the Calendar onSelect callback (after setting the date). For range mode it is usual to leave the popover open until the user clicks outside, since they need two clicks. The wrapper does this for single dates automatically.
Use date-fns format: format(date, "PPP") gives "January 1, 2025"; "LLL dd, y" gives "Jan 01, 2025"; "MMMM d, yyyy" gives "January 1, 2025". Show a muted placeholder when nothing is selected via data-empty={!date} and data-[empty=true]:text-muted-foreground on the trigger.