Date Picker

A flexible date picker component supporting single date and date range selection. Built on Radix UI Popover primitives with full keyboard navigation and accessibility.

Basic Usage

import { DatePicker } from '@/components/DatePicker'

<DatePicker.Root onValueChange={(date) => console.log(date)}>
  <DatePicker.Trigger asChild>
    <Button>Select Date</Button>
  </DatePicker.Trigger>
  <DatePicker.Content>
    <DatePicker.Calendar />
  </DatePicker.Content>
</DatePicker.Root>

Selected: None

Single Date with Apply Button

Use applyMode="onApply" to require confirmation before applying the selection.

Selected: None

<DatePicker.Root applyMode="onApply" onValueChange={setDate}>
  <DatePicker.Trigger asChild>
    <Button>Select Date</Button>
  </DatePicker.Trigger>
  <DatePicker.Content>
    <div className="flex flex-col gap-[30px]">
      <DatePicker.Calendar />
      <DatePicker.Footer />
    </div>
  </DatePicker.Content>
</DatePicker.Root>

Date Range Picker

Use mode="range" to enable date range selection with two calendars side-by-side.

Selected: None

<DatePicker.Root mode="range" applyMode="onApply" onRangeChange={setRange}>
  <DatePicker.Trigger asChild>
    <Button>Select Range</Button>
  </DatePicker.Trigger>
  <DatePicker.Content>
    <DatePicker.Presets />
    <div className="flex flex-col gap-[30px]">
      <div className="flex gap-8">
        <DatePicker.Calendar month="current" />
        <DatePicker.Calendar month="next" />
      </div>
      <div className="flex items-center justify-between">
        <DatePicker.RangeDisplay />
        <DatePicker.Footer />
      </div>
    </div>
  </DatePicker.Content>
</DatePicker.Root>

Range Picker Without Presets

You can omit the DatePicker.Presets component for a simpler range picker.

Selected: None

Min/Max Dates

Restrict selectable dates using minDate and maxDate props.

Min: Nov 1 | Max: Dec 31, 2025

Selected: None

<DatePicker.Root
  minDate={new Date(2024, 0, 1)}
  maxDate={new Date(2024, 11, 31)}
  onValueChange={setDate}
>
  ...
</DatePicker.Root>

Marked Dates

Highlight specific dates with dot indicators using the markedDates prop.

Marked dates: 5th, 12th, 20th, 25th (shown with dots)

Selected: None

const importantDates = [
  new Date(2024, 5, 5),
  new Date(2024, 5, 12),
  new Date(2024, 5, 20),
]

<DatePicker.Root markedDates={importantDates} onValueChange={setDate}>
  ...
</DatePicker.Root>

Controlled

Control the date value and open state externally.

Value: November 28th, 2025 | Open: No

const [date, setDate] = useState<Date | null>(new Date())
const [open, setOpen] = useState(false)

<DatePicker.Root
  value={date}
  onValueChange={setDate}
  open={open}
  onOpenChange={setOpen}
>
  ...
</DatePicker.Root>

Custom Presets

Provide custom preset options for the range picker.

Custom presets: This Week, Next Week, etc.

Selected: None

const customPresets = [
  {
    label: 'This Week',
    getValue: () => {
      const now = new Date()
      const start = startOfWeek(now)
      const end = endOfWeek(now)
      return { start, end }
    },
  },
  {
    label: 'Next Month',
    getValue: () => ({
      start: startOfMonth(addMonths(new Date(), 1)),
      end: endOfMonth(addMonths(new Date(), 1)),
    }),
  },
]

<DatePicker.Root mode="range" presets={customPresets}>
  ...
</DatePicker.Root>

Accessibility

  • Built on Radix UI Popover primitive for robust accessibility
  • Full keyboard support (Arrow keys to navigate, Enter/Space to select, Escape to close)
  • Proper ARIA attributes for screen readers
  • Focus management within the calendar
  • Month/year navigation with keyboard shortcuts

API Reference

DatePicker.Root Props

PropTypeDefaultDescription
mode'single' | 'range''single'Selection mode
applyMode'immediate' | 'onApply''immediate'When to apply selection
valueDate | null-Controlled single date value
defaultValueDate | null-Default single date (uncontrolled)
onValueChange(date: Date | null) => void-Callback when single date changes
startValueDate | null-Controlled range start value
endValueDate | null-Controlled range end value
onRangeChange(start: Date | null, end: Date | null) => void-Callback when range changes
minDateDate-Minimum selectable date
maxDateDate-Maximum selectable date
markedDatesDate[][]Dates to mark with indicators
disabledbooleanfalseDisable the date picker
openboolean-Controlled open state
defaultOpenbooleanfalseDefault open state
onOpenChange(open: boolean) => void-Callback when open state changes
presetsDatePreset[]Default presetsCustom preset options

DatePicker.Trigger Props

PropTypeDefaultDescription
asChildbooleanfalseRender as child element
disabledboolean-Override disabled state

DatePicker.Content Props

PropTypeDefaultDescription
align'start' | 'center' | 'end''start'Alignment relative to trigger
sideOffsetnumber8Distance from trigger
containerHTMLElement-Portal container

DatePicker.Calendar Props

PropTypeDefaultDescription
month'current' | 'next''current'Which month to display (for range mode)

DatePicker.Presets Props

Default presets include: Today, Last 7 days, Last 30 days, Last 3 months, Last 12 months, Month to date, Year to date, All time.

DatePreset Type

interface DatePreset {
  label: string
  getValue: () => { start: Date; end: Date }
}