Date Picker
A date picker component built with the Calendar and Menu components.
import { format } from "date-fns";
import { useState } from "react";
import { DatePicker } from "@/components/date-picker";
export default function DatePickerPreview() {
const [selectedDate, setSelectedDate] = useState<Date | null>(null);
return (
<DatePicker placement="bottom-start">
<DatePicker.Trigger className="w-60" placeholder="Select date">
{selectedDate ? format(selectedDate, "PPP") : undefined}
</DatePicker.Trigger>
<DatePicker.Panel
className="w-72"
value={selectedDate}
onDateChange={(date: Date) => {
setSelectedDate(date);
}}
/>
</DatePicker>
);
}Dependencies
Source Code
import { CalendarIcon, CaretUpDownIcon } from "@phosphor-icons/react/dist/ssr";
import type { VariantProps } from "cva";
import { Calendar } from "@/components/calendar";
import { inputStyle } from "@/components/input";
import { Menu, useMenuPopoverContext } from "@/components/menu";
import { cn } from "@/lib/utils/classnames";
const DatePicker = ({ children, ...props }: React.ComponentProps<typeof Menu>) => {
return <Menu {...props}>{children}</Menu>;
};
interface DatePickerTriggerProps extends React.ComponentProps<typeof Menu.Trigger> {
className?: string;
children: React.ReactNode;
variant?: VariantProps<typeof inputStyle>["variant"];
placeholder?: string;
}
const DatePickerTrigger = ({
children,
className,
variant,
placeholder,
...props
}: DatePickerTriggerProps) => {
return (
<Menu.Trigger 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>
</Menu.Trigger>
);
};
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 } = useMenuPopoverContext();
return (
<Menu.Items 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}
</Menu.Items>
);
};
const CompoundDatePicker = Object.assign(DatePicker, {
Trigger: DatePickerTrigger,
Panel: DatePickerPanel,
});
export { CompoundDatePicker as DatePicker };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>
<DatePicker.Trigger />
<DatePicker.Panel />
</DatePicker>
API Reference
DatePicker
Extends the Menu component.
DatePicker.Trigger
Extends the Menu.Trigger 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. |
DatePicker.Panel
Extends the Calendar component.
Examples
Simple Date Picker
Basic usage for selecting a date.
import { format } from "date-fns";
import { useState } from "react";
import { DatePicker } from "@/components/date-picker";
export default function DatePickerPreview() {
const [selectedDate, setSelectedDate] = useState<Date | null>(null);
return (
<DatePicker placement="bottom-start">
<DatePicker.Trigger className="w-60" placeholder="Select date">
{selectedDate ? format(selectedDate, "PPP") : undefined}
</DatePicker.Trigger>
<DatePicker.Panel
className="w-72"
value={selectedDate}
onDateChange={(date: Date) => {
setSelectedDate(date);
}}
/>
</DatePicker>
);
}Date Picker with Time
Adding time selection capability to the date picker.
import { format } from "date-fns";
import { useState } from "react";
import { DatePicker } 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">
<DatePicker.Trigger className="w-60" placeholder="Select date">
{format(selectedDate, "PPP")}
</DatePicker.Trigger>
<DatePicker.Panel
className="w-72"
value={selectedDate || new Date()}
onDateChange={(date: Date) => {
setSelectedDate(date);
}}
/>
</DatePicker>
<Input className="w-28" type="time" step="1" />
</div>
);
}Shortcuts
Using predefined shortcuts for quick date selection.
import { format } from "date-fns";
import { useState } from "react";
import { DatePicker } from "@/components/date-picker";
import { Menu } from "@/components/menu";
export default function DatePickerShortcutsPreview() {
const [dateRange, setDateRange] = useState<[Date, Date] | null>(null);
return (
<DatePicker placement="bottom-start">
<DatePicker.Trigger className="w-80" placeholder="Select date range">
{dateRange
? `${format(dateRange[0], "MM/dd/yyyy")} - ${format(dateRange[1], "MM/dd/yyyy")}`
: undefined}
</DatePicker.Trigger>
<DatePicker.Panel
className="w-80"
mode="range"
value={dateRange}
onDateChange={(dates: [Date, Date]) => {
setDateRange(dates);
}}
>
<Menu.Divider />
<Menu.Item
onSelect={() => {
setDateRange([new Date(), new Date()]);
}}
>
Today
</Menu.Item>
<Menu.Item
onSelect={() => {
setDateRange([
new Date(new Date().setDate(new Date().getDate() - 1)),
new Date(new Date().setDate(new Date().getDate() - 1)),
]);
}}
>
Yesterday
</Menu.Item>
<Menu.Item
onSelect={() => {
setDateRange([new Date(new Date().setDate(new Date().getDate() - 7)), new Date()]);
}}
>
Last 7 days
</Menu.Item>
<Menu.Divider />
<Menu.Item
variant="destructive"
onSelect={() => {
setDateRange(null);
}}
>
Clear
</Menu.Item>
</DatePicker.Panel>
</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