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
| Prop | Type | Default | Description |
|---|---|---|---|
| mode | 'single' | 'range' | 'single' | Selection mode |
| applyMode | 'immediate' | 'onApply' | 'immediate' | When to apply selection |
| value | Date | null | - | Controlled single date value |
| defaultValue | Date | null | - | Default single date (uncontrolled) |
| onValueChange | (date: Date | null) => void | - | Callback when single date changes |
| startValue | Date | null | - | Controlled range start value |
| endValue | Date | null | - | Controlled range end value |
| onRangeChange | (start: Date | null, end: Date | null) => void | - | Callback when range changes |
| minDate | Date | - | Minimum selectable date |
| maxDate | Date | - | Maximum selectable date |
| markedDates | Date[] | [] | Dates to mark with indicators |
| disabled | boolean | false | Disable the date picker |
| open | boolean | - | Controlled open state |
| defaultOpen | boolean | false | Default open state |
| onOpenChange | (open: boolean) => void | - | Callback when open state changes |
| presets | DatePreset[] | Default presets | Custom preset options |
DatePicker.Trigger Props
| Prop | Type | Default | Description |
|---|---|---|---|
| asChild | boolean | false | Render as child element |
| disabled | boolean | - | Override disabled state |
DatePicker.Content Props
| Prop | Type | Default | Description |
|---|---|---|---|
| align | 'start' | 'center' | 'end' | 'start' | Alignment relative to trigger |
| sideOffset | number | 8 | Distance from trigger |
| container | HTMLElement | - | Portal container |
DatePicker.Calendar Props
| Prop | Type | Default | Description |
|---|---|---|---|
| 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 }
}