Date Picker
Preline-style single-date picker with integrated segmented field, calendar icon, and Radix Popover with plain Date values.
Date Picker combines a native-like segmented field (React Aria year/month/day slots) and a calendar icon inside one bordered shell, with a VCP-powered Calendar in a Preline-inspired popover. Values stay plain Date.
Interactions
| Event | Behavior |
|---|---|
| Calendar trigger click | Toggle popover |
| Focus any date segment | Open popover |
| Edit segments while popover is open | Popover stays open; calendar month and selection sync to the typed date |
| Select day (no time) | Commit date, close popover |
Select day (withTime) | Commit date, keep popover open for time |
| Outside click / Escape | Close popover (withTime commits draft on close) |
Installing
pnpm dlx shadcn@latest add https://ui.isaacfei.com/r/date-picker.jsonnpx shadcn@latest add https://ui.isaacfei.com/r/date-picker.jsonyarn dlx shadcn@latest add https://ui.isaacfei.com/r/date-picker.jsonbun x shadcn@latest add https://ui.isaacfei.com/r/date-picker.jsonOr with a namespace: npx shadcn@latest add @f-ui/date-picker.
Usage
import { DatePicker } from "@/components/f-ui/date-picker/date-picker";
<DatePicker
label="Date"
value={date}
onChange={setDate}
showToday
/>;Examples
Single Date
Default Preline-style picker with optional description and Today footer action.
Choose a day for your booking.
Selected: —
"use client";
import { useState } from "react";
import { DatePicker } from "@/components/f-ui/date-picker/date-picker";
export function DatePickerDemo() {
const [date, setDate] = useState<Date | undefined>(undefined);
return (
<div className="max-w-sm space-y-3">
<DatePicker
label="Date"
value={date ?? null}
onChange={setDate}
description="Choose a day for your booking."
showToday
/>
<p className="text-muted-foreground text-xs">
Selected:{" "}
<span className="text-foreground font-medium tabular-nums">
{date ? date.toLocaleDateString() : "—"}
</span>
</p>
</div>
);
}Single Date + Time
Enables the VCP time row via withTime. The popover stays open after day selection until you finish time or dismiss.
Selected: —
"use client";
import { useState } from "react";
import { DatePicker } from "@/components/f-ui/date-picker/date-picker";
export function DatePickerWithTimeDemo() {
const [date, setDate] = useState<Date | undefined>(undefined);
return (
<div className="max-w-sm space-y-3">
<DatePicker
label="Date and time"
value={date ?? null}
onChange={setDate}
withTime
/>
<p className="text-muted-foreground text-xs">
Selected:{" "}
<span className="text-foreground font-medium tabular-nums">
{date ? date.toLocaleString() : "—"}
</span>
</p>
</div>
);
}Constrained Date
minValue, maxValue, and isDateUnavailable bound both the segmented input and calendar selection.
Weekdays only, within the next 3 months.
Selected: —
"use client";
import { useState } from "react";
import { DatePicker } from "@/components/f-ui/date-picker/date-picker";
const now = new Date();
const minDate = new Date(now.getFullYear(), now.getMonth(), 1);
const maxDate = new Date(now.getFullYear(), now.getMonth() + 3, 0);
function isWeekend(date: Date) {
const day = date.getDay();
return day === 0 || day === 6;
}
export function DatePickerConstrainedDemo() {
const [date, setDate] = useState<Date | undefined>(undefined);
return (
<div className="max-w-sm space-y-3">
<DatePicker
label="Appointment"
value={date ?? null}
onChange={setDate}
minValue={minDate}
maxValue={maxDate}
isDateUnavailable={isWeekend}
description="Weekdays only, within the next 3 months."
/>
<p className="text-muted-foreground text-xs">
Selected:{" "}
<span className="text-foreground font-medium tabular-nums">
{date ? date.toLocaleDateString() : "—"}
</span>
</p>
</div>
);
}Inside a Dialog
Use the picker inside shadcn Dialog for forms and wizards. Raise the calendar popover above the dialog layer with classNames.popover (default panel uses z-40; Dialog overlay/content use z-50).
Selected: —
"use client";
import { useState } from "react";
import { DatePicker } from "@/components/f-ui/date-picker/date-picker";
import { Button } from "@/components/ui/button";
import {
Dialog,
DialogContent,
DialogDescription,
DialogHeader,
DialogTitle,
DialogTrigger,
} from "@/components/ui/dialog";
export function DatePickerDialogDemo() {
const [date, setDate] = useState<Date | undefined>(undefined);
return (
<div className="max-w-sm space-y-3">
<Dialog>
<DialogTrigger asChild>
<Button variant="outline">Pick date in dialog</Button>
</DialogTrigger>
<DialogContent className="sm:max-w-md">
<DialogHeader>
<DialogTitle>Schedule date</DialogTitle>
<DialogDescription>
Pick a date inside this dialog and continue.
</DialogDescription>
</DialogHeader>
<DatePicker
label="Date"
value={date ?? null}
onChange={setDate}
showToday
classNames={{
// Popover portals to document body; sit above Dialog (z-50).
popover: "z-[60]",
}}
/>
</DialogContent>
</Dialog>
<p className="text-muted-foreground text-xs">
Selected:{" "}
<span className="text-foreground font-medium tabular-nums">
{date ? date.toLocaleDateString() : "—"}
</span>
</p>
</div>
);
}Headless Usage
Use useDatePicker for open/close state, field props, and calendar props. Build your own shell with plain HTML (or your design system). Wire DatePickerControl and Calendar from f-ui; use DatePickerPopover (or DateFieldPopoverContent) so the VCP grid mounts in a Radix popover panel.
Committed value: —
"use client";
import { useState } from "react";
import { Calendar } from "@/components/f-ui/calendar/calendar";
import { DatePickerControl } from "@/components/f-ui/date-picker/date-picker-parts/control";
import { DatePickerPopover } from "@/components/f-ui/date-picker/date-picker-parts/popover";
import { DatePickerTrigger } from "@/components/f-ui/date-picker/date-picker-parts/trigger";
import { useDatePickerI18n } from "@/components/f-ui/date-picker/hooks/use-date-picker-i18n";
import { useDatePicker } from "@/components/f-ui/date-picker/use-date-picker";
import { Popover, PopoverAnchor, PopoverTrigger } from "@/components/ui/popover";
export function DatePickerHeadlessDemo() {
const [date, setDate] = useState<Date | undefined>(undefined);
const { t, locale } = useDatePickerI18n();
const picker = useDatePicker({
value: date ?? null,
onChange: setDate,
locale,
});
const committedLabel = date ? date.toLocaleDateString(locale) : "—";
return (
<Popover open={picker.isOpen} onOpenChange={picker.onOpenChange}>
<div className="max-w-sm">
<label htmlFor="headless-date-picker">Date</label>
<PopoverAnchor asChild>
<div id="headless-date-picker" className="mt-1 min-w-0">
<DatePickerControl
{...picker.fieldProps}
aria-label={t("ariaLabel")}
calendarTrigger={
<PopoverTrigger asChild>
<DatePickerTrigger
aria-label={t("openCalendar")}
onClick={picker.onTriggerClick}
/>
</PopoverTrigger>
}
/>
</div>
</PopoverAnchor>
<p className="mt-2 text-sm">
Committed value: <strong>{committedLabel}</strong>
</p>
</div>
<DatePickerPopover className="w-fit max-w-[min(100vw-2rem,100%)] overflow-hidden p-0">
<Calendar
{...picker.calendarProps}
showToday
classNames={{ root: "border-0 bg-transparent shadow-none" }}
/>
</DatePickerPopover>
</Popover>
);
}You can also compose from lower-level pieces without the hook — manage open state yourself and close on day pick:
Committed value: —
"use client";
import { useState } from "react";
import { Calendar } from "@/components/f-ui/calendar/calendar";
import { DatePickerControl } from "@/components/f-ui/date-picker/date-picker-parts/control";
import { DatePickerPopover } from "@/components/f-ui/date-picker/date-picker-parts/popover";
import { Popover, PopoverAnchor, PopoverTrigger } from "@/components/ui/popover";
export function DatePickerHtmlDemo() {
const [date, setDate] = useState<Date | undefined>(undefined);
const [open, setOpen] = useState(false);
return (
<Popover open={open} onOpenChange={setOpen}>
<div className="max-w-sm">
<label htmlFor="html-date-picker">Date</label>
<PopoverAnchor asChild>
<div id="html-date-picker" className="mt-1 min-w-0">
<DatePickerControl
value={date ?? null}
onChange={setDate}
onFocus={() => setOpen(true)}
aria-label="Date"
calendarTrigger={
<PopoverTrigger asChild>
<button type="button" onClick={() => setOpen((prev) => !prev)}>
Calendar
</button>
</PopoverTrigger>
}
/>
</div>
</PopoverAnchor>
<p className="mt-2 text-sm">
Committed value:{" "}
<strong>{date ? date.toLocaleDateString() : "—"}</strong>
</p>
</div>
<DatePickerPopover className="w-fit max-w-[min(100vw-2rem,100%)] overflow-hidden p-0">
<Calendar
mode="single"
value={date ?? null}
onChange={(next) => {
setDate(next);
setOpen(false);
}}
showToday
classNames={{ root: "border-0 bg-transparent shadow-none" }}
/>
</DatePickerPopover>
</Popover>
);
}Composition
DatePicker
├── Label (optional)
├── DatePickerControl (segmented field + calendar icon)
│ └── PopoverTrigger → DatePickerTrigger
├── DatePickerPopover
│ └── Calendar mode="single"
└── Description / Error messageAPI Reference
Props
| Prop | Type | Default |
|---|---|---|
value | Date | null | — |
onChange | (date: Date | undefined) => void | — |
onBlur | () => void | — |
locale | string | i18n/provider locale |
withTime | boolean | false |
minValue / maxValue | Date | — |
isDateUnavailable | (date: Date) => boolean | — |
firstDayOfWeek | 0 | 1 | 2 | 3 | 4 | 5 | 6 | locale default |
disabled | boolean | false |
isInvalid | boolean | false |
label | ReactNode | — |
aria-label | string | localized ariaLabel |
description | ReactNode | — |
errorMessage | ReactNode | — |
showToday | boolean | calendar default |
className | string | — |
classNames | Partial<Record<DatePickerSlot, string>> | — |
t | DatePickerTranslateFn | built-in/provider |
Slots
| Slot | Element |
|---|---|
root | Outer wrapper |
field | Field wrapper |
input | Segmented date input inside control |
icon | Calendar trigger button |
popover | Popover panel |
calendar | Embedded Calendar |
description | Description text |
errorMessage | Error text |
Hook — useDatePicker(options)
| Option | Type |
|---|---|
value | Date | null |
onChange | (date: Date | undefined) => void |
onBlur | () => void |
locale | string (required) |
withTime | boolean |
minValue / maxValue | Date |
isDateUnavailable | (date: Date) => boolean |
firstDayOfWeek | 0–6 |
disabled | boolean |
isInvalid | boolean |
| Return | Type |
|---|---|
value | committed Date |
isOpen / setIsOpen | popover open state |
onOpenChange | (open: boolean) => void |
onTriggerClick | toggle handler for your trigger button |
fieldProps | spread on DatePickerControl (omit calendarTrigger) |
calendarProps | spread on Calendar (mode: "single") |