# Shadcn Date Picker

> Free shadcn/ui date picker for React — a reusable <DatePicker> 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.

Source: https://designrevision.com/components/date-picker

The **Shadcn Date Picker** is a date field that opens a calendar — the [Calendar](/components/calendar) inside a [Popover](/components/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:

```tsx
<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](/components/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](/components/table) vs [Data Table](/components/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](/components/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 <a href="https://date-fns.org/" rel="nofollow">date-fns</a> `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](/components/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.

## Installation

### date-picker

**Install:**

```bash
npx shadcn@latest add @designrevision/date-picker
```

**Dependencies:** date-fns, lucide-react, utils, popover, button, calendar

**Props**

| Prop | Type | Default | Description |
| --- | --- | --- | --- |
| value | Date \| undefined | — | The selected date (controlled). |
| onValueChange | (date: Date \| undefined) => void | — | Fires when a date is picked; the popover closes on select. |
| placeholder | string | "Pick a date" | Trigger text shown when no date is selected. |
| disabled | boolean | false | Disables the trigger. |

**Usage & accessibility:** 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).

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

**Install:**

```bash
npx shadcn@latest add @designrevision/date-picker-demo-01
```

**Dependencies:** @designrevision/date-picker

```tsx
// 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.

**Install:**

```bash
npx shadcn@latest add @designrevision/date-picker-composition-01
```

**Dependencies:** date-fns, lucide-react, popover, button, calendar

```tsx
// 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.

**Install:**

```bash
npx shadcn@latest add @designrevision/date-picker-range-01
```

**Dependencies:** date-fns, lucide-react, popover, button, calendar

```tsx
// 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.

**Install:**

```bash
npx shadcn@latest add @designrevision/date-picker-dob-01
```

**Dependencies:** date-fns, lucide-react, popover, button, calendar, label

```tsx
// 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.

**Install:**

```bash
npx shadcn@latest add @designrevision/date-picker-input-01
```

**Dependencies:** date-fns, lucide-react, popover, button, calendar, input, label

```tsx
// 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.

**Install:**

```bash
npx shadcn@latest add @designrevision/date-picker-form-01
```

**Dependencies:** date-fns, lucide-react, popover, button, calendar, label

```tsx
// 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.

**Install:**

```bash
npx shadcn@latest add @designrevision/date-picker-presets-01
```

**Dependencies:** date-fns, lucide-react, popover, button, calendar

```tsx
// 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.

**Install:**

```bash
npx shadcn@latest add @designrevision/date-picker-disabled-01
```

**Dependencies:** date-fns, lucide-react, popover, button, calendar, label

```tsx
// 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>
  )
}
```
