Date Picker

Single dates and date ranges in a calendar popover.

Preview

Installation

pnpm dlx shadcn@latest add http://127.0.0.1:3017/r/date-picker.json

Installs the picker source, Calendar, Popover, and Button with their dependencies. Field is a separate component used in these examples. Keep tw-animate-css imported for popover animations. The endpoint is local; see the registry guide.

Usage

'use client';

import * as React from 'react';
import { DatePicker } from '@/components/ui/date-picker';
import { Field, FieldLabel } from '@/components/ui/field';

export function DueDate() {
  const [date, setDate] = React.useState<Date>();
  return (
    <Field>
      <FieldLabel htmlFor="due-date">Due date</FieldLabel>
      <DatePicker
        id="due-date"
        value={date}
        onValueChange={setDate}
        name="dueDate"
      />
    </Field>
  );
}

Both triggers use an 8px inline-start icon inset and an 8px icon-to-text gap, matching Input Group. They remain buttons that open a calendar.

Both pickers are controlled: store the selection in the parent. Selecting a single date closes the panel and returns focus to the trigger. Provide an external clear action by setting the value to undefined when your workflow allows clearing.

This packages the shadcn Date Picker composition into reusable components using Migoy's existing calendar and popover.

Composition

DatePicker and DateRangePicker package the same Calendar and Popover building blocks:

Field
├── FieldLabel
└── DatePicker or DateRangePicker
    └── Popover (internal)
        ├── PopoverTrigger → Button
        └── PopoverContent
            ├── PopoverTitle (visually hidden)
            ├── Calendar
            └── PopoverClose → Apply range (range picker only)

Associate FieldLabel with the picker's id. The parent owns value and onValueChange; each picker manages its own popup visibility. Pass calendar configuration through calendarProps rather than nesting Calendar inside a picker. Optional named hidden inputs are rendered alongside the popover for form submission.

For a custom layout, compose Popover and Calendar directly. Use PopoverTrigger's Base UI render prop with Button, provide a PopoverTitle, and place Calendar inside PopoverContent. Keep selection state in your wrapper and decide when selection should close the popup.

Range picker

Select a start and end date, then apply the range.

import type { DateRange } from 'react-day-picker';
import { DateRangePicker } from '@/components/ui/date-picker';

const [range, setRange] = React.useState<DateRange | undefined>();

<DateRangePicker
  aria-label="Reporting period"
  value={range}
  onValueChange={setRange}
  startName="from"
  endName="to"
/>

Range changes update the parent immediately. Apply range closes the panel once both dates exist; Escape or clicking outside also closes it and retains the current selection. Apply does not submit a form or save to a server. Months stack on narrow screens.

Date of birth

Use calendarProps to configure dropdown navigation, disabled dates, locale, or navigation bounds. Navigation bounds and selectable dates are separate settings.

<DatePicker
  aria-label="Date of birth"
  value={date}
  onValueChange={setDate}
  calendarProps={{
    captionLayout: 'dropdown',
    startMonth: new Date(1920, 0),
    endMonth: new Date(2026, 10),
    disabled: { after: new Date(2026, 10, 1) },
  }}
/>

The demos use a fixed November 2026 date for repeatable previews. In a product, derive date limits from your application's current date and timezone.

Disabled

API and forms

PropPurpose
value, onValueChangeControlled Date or DateRange; either may be undefined
placeholderEmpty trigger text and accessible popup title
id, aria-label, aria-labelledbyLabel the trigger
aria-describedby, aria-invalidConnect helper text or validation
disabledDisable the trigger and omit hidden form values
calendarPropsDayPicker settings excluding selection ownership
nameSingle-date hidden form field
startName, endNameRange hidden form fields

Named values serialize as local date-only yyyy-MM-dd strings; empty selections submit empty strings. Hidden inputs do not enforce native required validation. Validate required dates, complete ranges, and allowed dates in your form logic and again on the server. Reset parent state explicitly when resetting a form. Use local date constructors instead of UTC date-string parsing for date-only values.

Accessibility

  • Associate a visible FieldLabel with the trigger's id, or supply an accessible name.
  • Enter or Space opens the picker and focuses the calendar.
  • Arrow keys move between days; Enter or Space selects a date.
  • Escape dismisses the popup and returns focus to the trigger.
  • Calendar titles name the popup; disabled days retain DayPicker's accessible state.

See React DayPicker for calendar selection and keyboard behavior.

On this page