Date Picker
A date picker component built with the Calendar and Dropdown components.
'use client';
import { format } from 'date-fns';
import { useState } from 'react';
import {
DatePicker,
DatePickerPanel,
DatePickerTrigger,
} from '@/components/date-picker';
export default function DatePickerPreview() {
const [selectedDate, setSelectedDate] = useState<Date | null>(null);
return (
<DatePicker placement="bottom-start">
<DatePickerTrigger className="w-60" placeholder="Select date">
{selectedDate ? format(selectedDate, 'PPP') : undefined}
</DatePickerTrigger>
<DatePickerPanel
className="w-72"
value={selectedDate}
onDateChange={(date: Date) => {
setSelectedDate(date);
}}
/>
</DatePicker>
);
} Dependencies
Source Code
'use client';
import { CalendarIcon, CaretUpDownIcon } from '@phosphor-icons/react';
import type { VariantProps } from 'cva';
import { Calendar } from '@/components/calendar';
import {
Dropdown,
DropdownItems,
DropdownTrigger,
useDropdownContext,
} from '@/components/dropdown';
import { inputStyle } from '@/components/input';
import { cn } from '@/lib/utils/classnames';
const DatePicker = ({
children,
...props
}: React.ComponentProps<typeof Dropdown>) => {
return <Dropdown {...props}>{children}</Dropdown>;
};
interface DatePickerTriggerProps
extends React.ComponentProps<typeof DropdownTrigger> {
className?: string;
children: React.ReactNode;
variant?: VariantProps<typeof inputStyle>['variant'];
placeholder?: string;
}
const DatePickerTrigger = ({
children,
className,
variant,
placeholder,
...props
}: DatePickerTriggerProps) => {
return (
<DropdownTrigger asChild {...props}>
<button
type="button"
className={cn(
inputStyle({ variant }),
'flex items-center gap-1.5 enabled:cursor-pointer',
'relative w-full pr-10 pl-4',
className
)}
>
<CalendarIcon className="shrink-0 text-foreground-secondary" />
{children ?? (
<span className="text-foreground-secondary">{placeholder}</span>
)}
<CaretUpDownIcon
weight="bold"
className="absolute top-1/2 right-3 -translate-y-1/2 text-base text-foreground/80"
/>
</button>
</DropdownTrigger>
);
};
interface DatePickerContentCommonProps
extends Omit<
React.ComponentPropsWithRef<typeof Calendar>,
'mode' | 'value' | 'onDateChange'
> {
className?: string;
children?: React.ReactNode;
}
interface DatePickerContentSingleProps extends DatePickerContentCommonProps {
mode?: 'single';
value: Date | null;
onDateChange: (date: Date) => void;
}
interface DatePickerContentRangeProps extends DatePickerContentCommonProps {
mode: 'range';
value: [Date, Date] | null;
onDateChange: (dates: [Date, Date]) => void;
}
type DatePickerContentProps =
| DatePickerContentSingleProps
| DatePickerContentRangeProps;
const DatePickerPanel = ({
className,
children,
mode,
value,
onDateChange,
...props
}: DatePickerContentProps) => {
const { setOpen } = useDropdownContext();
return (
<DropdownItems className={cn(className)}>
<Calendar
{...props}
mode={mode}
// biome-ignore lint/suspicious/noExplicitAny: expected
value={value as any}
onDateChange={(date: Date | [Date, Date]) => {
setOpen(false);
// biome-ignore lint/suspicious/noExplicitAny: expected
onDateChange(date as any);
}}
/>
{children}
</DropdownItems>
);
};
export { DatePicker, DatePickerPanel, DatePickerTrigger }; Features
- Flexible Input: Supports different input variants and placeholders
- Shortcuts: Predefined date shortcuts for quick selection
- Smart Positioning: Automatically adjusts panel position
- Keyboard Navigation: Full keyboard support inherited from Calendar
Anatomy
<DatePicker>
<DatePickerTrigger />
<DatePickerPanel />
</DatePicker>
API Reference
DatePicker
Extends the Dropdown component.
DatePickerTrigger
Extends the DropdownTrigger component.
| Prop | Default | Type | Description |
|---|---|---|---|
placeholder | - | string | The placeholder text to display when the date picker is empty. |
variant | - | InputProps["variant"] | Check [Input](/ui/input) for more information. |
DatePickerPanel
Extends the Calendar component.
Examples
Simple Date Picker
Basic usage for selecting a date.
'use client';
import { format } from 'date-fns';
import { useState } from 'react';
import {
DatePicker,
DatePickerPanel,
DatePickerTrigger,
} from '@/components/date-picker';
export default function DatePickerPreview() {
const [selectedDate, setSelectedDate] = useState<Date | null>(null);
return (
<DatePicker placement="bottom-start">
<DatePickerTrigger className="w-60" placeholder="Select date">
{selectedDate ? format(selectedDate, 'PPP') : undefined}
</DatePickerTrigger>
<DatePickerPanel
className="w-72"
value={selectedDate}
onDateChange={(date: Date) => {
setSelectedDate(date);
}}
/>
</DatePicker>
);
} Date Picker with Time
Adding time selection capability to the date picker.
'use client';
import { format } from 'date-fns';
import { useState } from 'react';
import {
DatePicker,
DatePickerPanel,
DatePickerTrigger,
} from '@/components/date-picker';
import { Input } from '@/components/input';
export default function DatePickerDateTimePreview() {
const [selectedDate, setSelectedDate] = useState<Date>(new Date());
return (
<div className="flex items-center gap-2">
<DatePicker placement="bottom-start">
<DatePickerTrigger className="w-60" placeholder="Select date">
{format(selectedDate, 'PPP')}
</DatePickerTrigger>
<DatePickerPanel
className="w-72"
value={selectedDate || new Date()}
onDateChange={(date: Date) => {
setSelectedDate(date);
}}
/>
</DatePicker>
<Input className="w-40" type="time" />
</div>
);
} Shortcuts
Using predefined shortcuts for quick date selection.
'use client';
import { format } from 'date-fns';
import { useState } from 'react';
import {
DatePicker,
DatePickerPanel,
DatePickerTrigger,
} from '@/components/date-picker';
import {
DropdownDivider,
DropdownItem,
} from '@/components/dropdown';
export default function DatePickerShortcutsPreview() {
const [dateRange, setDateRange] = useState<[Date, Date] | null>(null);
return (
<DatePicker placement="bottom-start">
<DatePickerTrigger className="w-80" placeholder="Select date range">
{dateRange
? `${format(dateRange[0], 'MM/dd/yyyy')} - ${format(dateRange[1], 'MM/dd/yyyy')}`
: undefined}
</DatePickerTrigger>
<DatePickerPanel
className="w-80"
mode="range"
value={dateRange}
onDateChange={(dates: [Date, Date]) => {
setDateRange(dates);
}}
>
<DropdownDivider />
<DropdownItem
onSelect={() => {
setDateRange([new Date(), new Date()]);
}}
>
Today
</DropdownItem>
<DropdownItem
onSelect={() => {
setDateRange([
new Date(new Date().setDate(new Date().getDate() - 1)),
new Date(new Date().setDate(new Date().getDate() - 1)),
]);
}}
>
Yesterday
</DropdownItem>
<DropdownItem
onSelect={() => {
setDateRange([
new Date(new Date().setDate(new Date().getDate() - 7)),
new Date(),
]);
}}
>
Last 7 days
</DropdownItem>
<DropdownDivider />
<DropdownItem
className="text-red-500"
onSelect={() => {
setDateRange(null);
}}
>
Clear
</DropdownItem>
</DatePickerPanel>
</DatePicker>
);
} Best Practices
-
Input Format:
- Use clear date format patterns
- Consider adding format hints in placeholder
- Handle invalid date inputs gracefully
-
Shortcuts:
- Keep shortcuts relevant to use case
- Use clear, descriptive labels
- Consider common date ranges
-
Mobile Considerations:
- Test touch interactions
- Consider using native pickers on mobile
- Ensure sufficient touch targets
Previous
Color Picker
Next
Dialog