"use client";
import { format } from "date-fns";
import { ChevronDownIcon } from "lucide-react";
import * as React from "react";
import { Button } from "@/components/ui/button";
import { Calendar } from "@/components/ui/calendar";
import {
Popover,
PopoverContent,
PopoverTrigger,
} from "@/components/ui/popover";
export function DatePickerDemo() {
const [date, setDate] = React.useState<Date>();
return (
<Popover>
<PopoverTrigger
render={
<Button
variant="tertiary"
data-empty={!date}
className="w-[212px] justify-between text-left font-normal data-[empty=true]:text-muted-foreground"
/>
}
>
{date ? format(date, "PPP") : <span>Pick a date</span>}
<ChevronDownIcon data-icon="inline-end" />
</PopoverTrigger>
<PopoverContent className="w-auto p-0" align="start">
<Calendar
mode="single"
selected={date}
onSelect={setDate}
defaultMonth={date}
/>
</PopoverContent>
</Popover>
);
}
The Date Picker is built using a composition of the <Popover /> and the <Calendar /> components.
"use client"
import * as React from "react"
import { format } from "date-fns"
import { CalendarIcon } from "lucide-react"
import { Button } from "@/components/ui/button"
import { Calendar } from "@/components/ui/calendar"
import {
Popover,
PopoverContent,
PopoverTrigger,
} from "@/components/ui/popover"
export function DatePickerDemo() {
const [date, setDate] = React.useState<Date>()
return (
<Popover>
<PopoverTrigger
render={
<Button
variant="outline"
data-empty={!date}
className="justify-start text-left font-normal data-[empty=true]:text-muted-foreground"
/>
}
>
<CalendarIcon />
{date ? format(date, "PPP") : <span>Pick a date</span>}
</PopoverTrigger>
<PopoverContent className="w-auto p-0">
<Calendar mode="single" selected={date} onSelect={setDate} />
</PopoverContent>
</Popover>
)
}See the React DayPicker documentation for more information.
A date picker is built from Popover and Calendar (there is no DatePicker root component):
Popover
├── PopoverTrigger
└── PopoverContent
└── CalendarA basic date picker component.
"use client";
import { format } from "date-fns";
import * as React from "react";
import { Button } from "@/components/ui/button";
import { Calendar } from "@/components/ui/calendar";
import { Field, FieldLabel } from "@/components/ui/field";
import {
Popover,
PopoverContent,
PopoverTrigger,
} from "@/components/ui/popover";
export function DatePickerSimple() {
const [date, setDate] = React.useState<Date>();
return (
<Field className="mx-auto w-44">
<FieldLabel htmlFor="date-picker-simple">Date</FieldLabel>
<Popover>
<PopoverTrigger
render={
<Button
variant="tertiary"
id="date-picker-simple"
className="justify-start font-normal"
/>
}
>
{date ? format(date, "PPP") : <span>Pick a date</span>}
</PopoverTrigger>
<PopoverContent className="w-auto p-0" align="start">
<Calendar
mode="single"
selected={date}
onSelect={setDate}
defaultMonth={date}
/>
</PopoverContent>
</Popover>
</Field>
);
}
A date picker component for selecting a range of dates.
"use client";
import { addDays, format } from "date-fns";
import { CalendarIcon } from "lucide-react";
import * as React from "react";
import type { DateRange } from "react-day-picker";
import { Button } from "@/components/ui/button";
import { Calendar } from "@/components/ui/calendar";
import { Field, FieldLabel } from "@/components/ui/field";
import {
Popover,
PopoverContent,
PopoverTrigger,
} from "@/components/ui/popover";
export function DatePickerWithRange() {
const [date, setDate] = React.useState<DateRange | undefined>({
from: new Date(new Date().getFullYear(), 0, 20),
to: addDays(new Date(new Date().getFullYear(), 0, 20), 20),
});
return (
<Field className="mx-auto w-60">
<FieldLabel htmlFor="date-picker-range">Date Picker Range</FieldLabel>
<Popover>
<PopoverTrigger
render={
<Button
variant="tertiary"
id="date-picker-range"
className="justify-start px-2.5 font-normal"
/>
}
>
<CalendarIcon data-icon="inline-start" />
{date?.from ? (
date.to ? (
<>
{format(date.from, "LLL dd, y")} -{" "}
{format(date.to, "LLL dd, y")}
</>
) : (
format(date.from, "LLL dd, y")
)
) : (
<span>Pick a date</span>
)}
</PopoverTrigger>
<PopoverContent className="w-auto p-0" align="start">
<Calendar
mode="range"
defaultMonth={date?.from}
selected={date}
onSelect={setDate}
numberOfMonths={2}
/>
</PopoverContent>
</Popover>
</Field>
);
}
A date picker component for selecting a date of birth. This component includes a dropdown caption layout for date and month selection.
"use client";
import * as React from "react";
import { Button } from "@/components/ui/button";
import { Calendar } from "@/components/ui/calendar";
import { Field, FieldLabel } from "@/components/ui/field";
import {
Popover,
PopoverContent,
PopoverTrigger,
} from "@/components/ui/popover";
export function DatePickerDob() {
const [open, setOpen] = React.useState(false);
const [date, setDate] = React.useState<Date | undefined>(undefined);
return (
<Field className="mx-auto w-44">
<FieldLabel htmlFor="date">Date of birth</FieldLabel>
<Popover open={open} onOpenChange={setOpen}>
<PopoverTrigger
render={
<Button
variant="tertiary"
id="date"
className="justify-start font-normal"
/>
}
>
{date ? date.toLocaleDateString() : "Select date"}
</PopoverTrigger>
<PopoverContent className="w-auto overflow-hidden p-0" align="start">
<Calendar
mode="single"
selected={date}
defaultMonth={date}
captionLayout="dropdown"
onSelect={(date) => {
setDate(date);
setOpen(false);
}}
/>
</PopoverContent>
</Popover>
</Field>
);
}
A date picker component with an input field for selecting a date.
"use client";
import { CalendarIcon } from "lucide-react";
import * as React from "react";
import { Calendar } from "@/components/ui/calendar";
import { Field, FieldLabel } from "@/components/ui/field";
import {
InputGroup,
InputGroupAddon,
InputGroupButton,
InputGroupInput,
} from "@/components/ui/input-group";
import {
Popover,
PopoverContent,
PopoverTrigger,
} from "@/components/ui/popover";
function formatDate(date: Date | undefined) {
if (!date) {
return "";
}
return date.toLocaleDateString("en-US", {
day: "2-digit",
month: "long",
year: "numeric",
});
}
function isValidDate(date: Date | undefined) {
if (!date) {
return false;
}
return !isNaN(date.getTime());
}
export function DatePickerInput() {
const [open, setOpen] = React.useState(false);
const [date, setDate] = React.useState<Date | undefined>(
new Date("2025-06-01"),
);
const [month, setMonth] = React.useState<Date | undefined>(date);
const [value, setValue] = React.useState(formatDate(date));
return (
<Field className="mx-auto w-48">
<FieldLabel htmlFor="date-required">Subscription Date</FieldLabel>
<InputGroup>
<InputGroupInput
id="date-required"
value={value}
placeholder="June 01, 2025"
onChange={(e) => {
const date = new Date(e.target.value);
setValue(e.target.value);
if (isValidDate(date)) {
setDate(date);
setMonth(date);
}
}}
onKeyDown={(e) => {
if (e.key === "ArrowDown") {
e.preventDefault();
setOpen(true);
}
}}
/>
<InputGroupAddon align="inline-end">
<Popover open={open} onOpenChange={setOpen}>
<PopoverTrigger
render={
<InputGroupButton
id="date-picker"
variant="ghost"
size="icon-xs"
aria-label="Select date"
/>
}
>
<CalendarIcon />
<span className="sr-only">Select date</span>
</PopoverTrigger>
<PopoverContent
className="w-auto overflow-hidden p-0"
align="end"
alignOffset={-8}
sideOffset={10}
>
<Calendar
mode="single"
selected={date}
month={month}
onMonthChange={setMonth}
onSelect={(date) => {
setDate(date);
setValue(formatDate(date));
setOpen(false);
}}
/>
</PopoverContent>
</Popover>
</InputGroupAddon>
</InputGroup>
</Field>
);
}
A date picker component with a time input field for selecting a time.
"use client";
import { format } from "date-fns";
import { ChevronDownIcon } from "lucide-react";
import * as React from "react";
import { Button } from "@/components/ui/button";
import { Calendar } from "@/components/ui/calendar";
import { Field, FieldGroup, FieldLabel } from "@/components/ui/field";
import { Input } from "@/components/ui/input";
import {
Popover,
PopoverContent,
PopoverTrigger,
} from "@/components/ui/popover";
export function DatePickerTime() {
const [open, setOpen] = React.useState(false);
const [date, setDate] = React.useState<Date | undefined>(undefined);
return (
<FieldGroup className="mx-auto max-w-xs flex-row">
<Field>
<FieldLabel htmlFor="date-picker-optional">Date</FieldLabel>
<Popover open={open} onOpenChange={setOpen}>
<PopoverTrigger
render={
<Button
variant="tertiary"
id="date-picker-optional"
className="w-32 justify-between font-normal"
/>
}
>
{date ? format(date, "PPP") : "Select date"}
<ChevronDownIcon data-icon="inline-end" />
</PopoverTrigger>
<PopoverContent className="w-auto overflow-hidden p-0" align="start">
<Calendar
mode="single"
selected={date}
captionLayout="dropdown"
defaultMonth={date}
onSelect={(sel) => {
setDate(sel);
setOpen(false);
}}
/>
</PopoverContent>
</Popover>
</Field>
<Field className="w-32">
<FieldLabel htmlFor="time-picker-optional">Time</FieldLabel>
<Input
type="time"
id="time-picker-optional"
step="1"
defaultValue="10:30:00"
className="appearance-none [&::-webkit-calendar-picker-indicator]:hidden [&::-webkit-calendar-picker-indicator]:appearance-none"
/>
</Field>
</FieldGroup>
);
}
This component uses the chrono-node library to parse natural language dates.
"use client";
import { parseDate } from "chrono-node";
import { CalendarIcon } from "lucide-react";
import * as React from "react";
import { Calendar } from "@/components/ui/calendar";
import { Field, FieldLabel } from "@/components/ui/field";
import {
InputGroup,
InputGroupAddon,
InputGroupButton,
InputGroupInput,
} from "@/components/ui/input-group";
import {
Popover,
PopoverContent,
PopoverTrigger,
} from "@/components/ui/popover";
function formatDate(date: Date | undefined) {
if (!date) {
return "";
}
return date.toLocaleDateString("en-US", {
day: "2-digit",
month: "long",
year: "numeric",
});
}
export function DatePickerNaturalLanguage() {
const [open, setOpen] = React.useState(false);
const [value, setValue] = React.useState("In 2 days");
const [date, setDate] = React.useState<Date | undefined>(
parseDate(value) || undefined,
);
return (
<Field className="mx-auto max-w-xs">
<FieldLabel htmlFor="date-optional">Schedule Date</FieldLabel>
<InputGroup>
<InputGroupInput
id="date-optional"
value={value}
placeholder="Tomorrow or next week"
onChange={(e) => {
setValue(e.target.value);
const date = parseDate(e.target.value);
if (date) {
setDate(date);
}
}}
onKeyDown={(e) => {
if (e.key === "ArrowDown") {
e.preventDefault();
setOpen(true);
}
}}
/>
<InputGroupAddon align="inline-end">
<Popover open={open} onOpenChange={setOpen}>
<PopoverTrigger
render={
<InputGroupButton
id="date-picker"
variant="ghost"
size="icon-xs"
aria-label="Select date"
/>
}
>
<CalendarIcon />
<span className="sr-only">Select date</span>
</PopoverTrigger>
<PopoverContent
className="w-auto overflow-hidden p-0"
align="end"
sideOffset={8}
>
<Calendar
mode="single"
selected={date}
captionLayout="dropdown"
defaultMonth={date}
onSelect={(sel) => {
setDate(sel);
setValue(formatDate(sel));
setOpen(false);
}}
/>
</PopoverContent>
</Popover>
</InputGroupAddon>
</InputGroup>
<div className="px-1 text-muted-foreground text-sm">
Your post will be published on{" "}
<span className="font-medium">{formatDate(date)}</span>.
</div>
</Field>
);
}
"use client";
import { format } from "date-fns";
import { arSA, he } from "date-fns/locale";
import { ChevronDownIcon } from "lucide-react";
import * as React from "react";
import {
arSA as arSADayPicker,
he as heDayPicker,
} from "react-day-picker/locale";
import {
type Translations,
useTranslation,
} from "@/components/language-selector";
import { Button } from "@/components/ui/button";
import { Calendar } from "@/components/ui/calendar";
import {
Popover,
PopoverContent,
PopoverTrigger,
} from "@/components/ui/popover";
const translations: Translations = {
en: {
dir: "ltr",
values: {
placeholder: "Pick a date",
},
},
ar: {
dir: "rtl",
values: {
placeholder: "اختر تاريخًا",
},
},
he: {
dir: "rtl",
values: {
placeholder: "בחר תאריך",
},
},
};
const dayPickerLocales = {
ar: arSADayPicker,
he: heDayPicker,
} as const;
const dateFnsLocales = {
ar: arSA,
he: he,
} as const;
export function DatePickerRtl() {
const { dir, t, language } = useTranslation(translations, "ar");
const [date, setDate] = React.useState<Date>();
const dateFnsLocale =
dir === "rtl"
? dateFnsLocales[language as keyof typeof dateFnsLocales]
: undefined;
const dayPickerLocale =
dir === "rtl"
? dayPickerLocales[language as keyof typeof dayPickerLocales]
: undefined;
return (
<Popover>
<PopoverTrigger
render={
<Button
variant="tertiary"
data-empty={!date}
className="w-[212px] justify-between text-left font-normal data-[empty=true]:text-muted-foreground"
dir={dir}
/>
}
>
{date ? (
format(date, "PPP", { locale: dateFnsLocale })
) : (
<span>{t.placeholder}</span>
)}
<ChevronDownIcon data-icon="inline-end" />
</PopoverTrigger>
<PopoverContent className="w-auto p-0" align="start" dir={dir}>
<Calendar
mode="single"
selected={date}
onSelect={setDate}
defaultMonth={date}
dir={dir}
locale={dayPickerLocale}
/>
</PopoverContent>
</Popover>
);
}
To enable RTL support in the date picker, import locales from both date-fns/locale and react-day-picker/locale, then pass dir, locale to the Calendar component and dir to the PopoverContent and PopoverTrigger:
import { arSA } from "date-fns/locale"
import { arSA as arSADayPicker } from "react-day-picker/locale"
<Popover>
<PopoverContent className="w-auto p-0" align="start" dir="rtl">
<Calendar
mode="single"
selected={date}
onSelect={setDate}
dir="rtl"
locale={arSADayPicker}
/>
</PopoverContent>
</Popover>