Date Picker
Single-date picker with segmented input and popover calendar — locale-aware and built on React Aria.
DatePicker uses React Aria for the field, popover, and calendar. The value is a plain JS Date. Pass locale (BCP 47) to localize segments and the calendar; without it the component inherits from the nearest I18nProvider or the browser default.
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.
The CLI installs npm dependencies (react-aria-components, @internationalized/date, lucide-react) and pulls registryDependencies: shared date-internals (calendar, date input, classes, value utils) plus shadcn primitives button, label, popover.
Usage
import { DatePicker } from '@/components/f-ui/date-picker/date-picker';
<DatePicker label="Date" value={date} onChange={setDate} />Examples
Basic — single date, calendar trigger, optional helper copy under the field.
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}
placeholder="Choose a day for your booking."
/>
<p className="text-muted-foreground text-xs">
Selected:{" "}
<span className="text-foreground font-medium tabular-nums">
{date ? date.toLocaleDateString() : "—"}
</span>
</p>
</div>
);
}Localized — switch locales to see month names, day headers, segment order, and first day of week.
Locale: en-US
Selected: —
"use client";
import { useState } from "react";
import { DatePicker } from "@/components/f-ui/date-picker/date-picker";
const locales = [
{ value: "en-US", label: "English (US)" },
{ value: "zh-CN", label: "中文 (简体)" },
{ value: "ja-JP", label: "日本語" },
{ value: "ar-SA", label: "العربية" },
{ value: "de-DE", label: "Deutsch" },
];
export function DatePickerI18nDemo() {
const [date, setDate] = useState<Date | undefined>(undefined);
const [locale, setLocale] = useState("en-US");
return (
<div className="max-w-sm space-y-4">
<div className="flex flex-wrap gap-1.5">
{locales.map((l) => (
<button
key={l.value}
type="button"
onClick={() => setLocale(l.value)}
className={`rounded-md border px-2.5 py-1 text-xs transition-colors ${
locale === l.value
? "border-primary bg-primary text-primary-foreground"
: "border-border bg-background text-muted-foreground hover:bg-accent"
}`}
>
{l.label}
</button>
))}
</div>
<DatePicker
locale={locale}
label="Date"
value={date ?? null}
onChange={setDate}
placeholder={`Locale: ${locale}`}
/>
<p className="text-muted-foreground text-xs">
Selected:{" "}
<span className="text-foreground font-medium tabular-nums">
{date ? date.toLocaleDateString(locale) : "—"}
</span>
</p>
</div>
);
}Constrained — only weekdays within a window; weekends and out-of-range dates unavailable.
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}
placeholder="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>
);
}Headless usage
useDatePicker returns { state, ariaProps }. Spread ariaProps onto <AriaDatePicker> and compose field, trigger, popover, and calendar however you like.
The default container keeps the trigger in the same row as the field, overlapping the right edge (DatePickerField uses pe-9; the trigger uses negative margin). For a different layout—for example a full-width field and a separate full-width “open calendar” control underneath—override field padding (e.g. className="pe-3" on DatePickerField) and trigger width (className="ms-0 w-full …" on DatePickerTrigger) so spacing matches your design. Add a visible <Label> when it helps; the live demo below uses a stacked layout and custom chrome.
You can go further and skip f-ui pieces entirely: spread the same ariaProps onto DatePicker from react-aria-components, then compose Group, DateInput / DateSegment, Button, Popover, Dialog, and Calendar (and grid/cell primitives) with plain styling—no DatePickerField, DatePickerTrigger, DatePickerPopover, or Calendar from this library. Use the Code tab on each demo below for the full source instead of duplicating it here.
Selected: —
"use client";
import { useState } from "react";
import {
DatePicker as AriaDatePicker,
I18nProvider,
Label,
} from "react-aria-components";
import { Calendar } from "@/components/f-ui/date-internals/calendar-rac";
import { DatePickerField } from "@/components/f-ui/date-picker/date-picker-parts/date-picker-field";
import { DatePickerPopover } from "@/components/f-ui/date-picker/date-picker-parts/date-picker-popover";
import { DatePickerTrigger } from "@/components/f-ui/date-picker/date-picker-parts/date-picker-trigger";
import { useDatePicker } from "@/components/f-ui/date-picker/use-date-picker";
/**
* Headless demo: same hook + parts as the default `DatePicker`, but a **different UI model** —
* segmented field full width, calendar action as a **separate full-width control** under the field
* (not overlapped on the right). Override field padding (`pe-3`) and trigger sizing (`w-full`)
* to match this layout; the default container keeps the icon inside the field chrome.
*/
export function DatePickerHeadlessDemo() {
const [date, setDate] = useState<Date | undefined>(undefined);
const { ariaProps } = useDatePicker({ value: date ?? null, onChange: setDate });
return (
<I18nProvider locale="en-US">
<AriaDatePicker
{...ariaProps}
className="flex max-w-md flex-col gap-3"
>
<div className="rounded-2xl border border-violet-500/25 bg-linear-to-br from-violet-500/[0.07] via-background to-cyan-500/6 p-4 shadow-sm dark:from-violet-500/15 dark:to-cyan-500/10">
<Label className="text-violet-700 dark:text-violet-300 mb-3 block text-[11px] font-semibold tracking-[0.12em] uppercase">
Headless · custom chrome
</Label>
<div className="flex flex-col gap-2">
<DatePickerField className="rounded-xl border-0 bg-background/90 pe-3 shadow-inner ring-1 ring-border" />
<DatePickerTrigger
aria-label="Open calendar"
className="ms-0 h-10 w-full min-w-0 shrink-0 justify-center gap-2 rounded-xl border border-violet-500/35 bg-violet-500/12 text-sm font-medium text-violet-800 shadow-none hover:bg-violet-500/20 dark:text-violet-200"
/>
</div>
</div>
<DatePickerPopover className="rounded-2xl shadow-xl ring-1 ring-violet-500/20">
<Calendar className="rounded-xl" />
</DatePickerPopover>
</AriaDatePicker>
<p className="text-muted-foreground mt-1 text-xs">
Selected:{" "}
<span className="text-foreground font-medium tabular-nums">
{date ? date.toLocaleDateString() : "—"}
</span>
</p>
</I18nProvider>
);
}Selected: —
"use client";
import { useState } from "react";
import {
Button,
Calendar,
CalendarCell,
CalendarGrid,
CalendarGridBody,
CalendarGridHeader,
CalendarHeaderCell,
DateInput,
DatePicker as AriaDatePicker,
DateSegment,
Dialog,
Group,
Heading,
I18nProvider,
Label,
Popover,
} from "react-aria-components";
import { useDatePicker } from "@/components/f-ui/date-picker/use-date-picker";
/**
* Same hook as the shipped `DatePicker`, but **no f-ui parts**: only `react-aria-components`
* and minimal styling on plain elements (native button via RAC `Button`, segmented field,
* table-based calendar grid).
*/
export function DatePickerHtmlDemo() {
const [date, setDate] = useState<Date | undefined>(undefined);
const { ariaProps } = useDatePicker({ value: date ?? null, onChange: setDate });
return (
<I18nProvider locale="en-US">
<AriaDatePicker {...ariaProps} className="flex max-w-md flex-col gap-2">
<Label className="text-foreground block text-sm font-medium">
Headless · RAC only (no f-ui parts)
</Label>
<Group className="flex flex-wrap items-center gap-2">
<DateInput className="text-foreground inline-flex min-h-9 min-w-0 flex-1 items-center rounded border border-neutral-400 bg-white px-2 py-1 font-mono text-sm dark:border-neutral-600 dark:bg-neutral-950">
{(segment) => (
<DateSegment
segment={segment}
className="data-[type=literal]:text-neutral-500 data-placeholder:text-neutral-400 rounded px-0.5 caret-transparent outline-none focus:bg-neutral-200 data-[type=literal]:px-0 dark:focus:bg-neutral-800"
/>
)}
</DateInput>
<Button
type="button"
aria-label="Open calendar"
className="h-9 shrink-0 rounded border border-neutral-400 bg-neutral-100 px-3 text-sm dark:border-neutral-600 dark:bg-neutral-900"
>
Calendar
</Button>
</Group>
<Popover
offset={6}
className="text-foreground rounded border border-neutral-400 bg-white shadow-md dark:border-neutral-600 dark:bg-neutral-950"
>
<Dialog className="p-2">
<Calendar>
<header className="mb-1 flex items-center gap-1">
<Button
slot="previous"
type="button"
className="rounded border border-transparent px-2 py-1 text-lg leading-none hover:bg-neutral-100 dark:hover:bg-neutral-800"
>
‹
</Button>
<Heading className="flex-1 text-center text-sm font-medium" />
<Button
slot="next"
type="button"
className="rounded border border-transparent px-2 py-1 text-lg leading-none hover:bg-neutral-100 dark:hover:bg-neutral-800"
>
›
</Button>
</header>
<CalendarGrid>
<CalendarGridHeader>
{(day) => (
<CalendarHeaderCell className="text-neutral-500 size-8 p-0 text-center text-xs">
{day}
</CalendarHeaderCell>
)}
</CalendarGridHeader>
<CalendarGridBody>
{(d) => (
<CalendarCell
date={d}
className="p-0.5 text-center text-sm data-disabled:opacity-30 data-unavailable:line-through data-unavailable:opacity-40 data-selected:bg-neutral-800 data-selected:text-white dark:data-selected:bg-neutral-200 dark:data-selected:text-neutral-900"
/>
)}
</CalendarGridBody>
</CalendarGrid>
</Calendar>
</Dialog>
</Popover>
</AriaDatePicker>
<p className="text-muted-foreground mt-1 text-xs">
Selected:{" "}
<span className="text-foreground font-medium tabular-nums">
{date ? date.toLocaleDateString() : "—"}
</span>
</p>
</I18nProvider>
);
}Composition
DatePicker (AriaDatePicker)
├── Label (optional)
├── div (flex row)
│ ├── DatePickerField (Group + DateInput)
│ └── DatePickerTrigger (Button + CalendarIcon)
├── DatePickerPopover (Popover + Dialog)
│ └── Calendar
└── helper text (optional, from `placeholder`)API Reference
Props
| Prop | Type | Default |
|---|---|---|
value | Date | null | — |
onChange | (date?: Date) => void | — |
onBlur | () => void | — |
locale | string (BCP 47) | — |
minValue | Date | — |
maxValue | Date | — |
isDateUnavailable | (date: Date) => boolean | — |
firstDayOfWeek | "sun" | "mon" | ... | "sat" | locale default |
disabled | boolean | false |
isInvalid | boolean | — |
aria-invalid | boolean | — |
label | React.ReactNode | — |
aria-label | string | "Date" when label is omitted |
placeholder | string (helper copy under the field) | — |
className | string (outer wrapper) | — |
classNames | Partial<Record<DatePickerSlot, string>> | — |
calendarTriggerAriaLabel | string | "Open calendar" |
Slots (classNames)
| Slot | Element |
|---|---|
root | Outer <AriaDatePicker> wrapper |
field | <DatePickerField> (segmented input group) |
trigger | <DatePickerTrigger> (calendar button) |
popover | <DatePickerPopover> (positioned panel) |
calendar | <Calendar> |
Hook — useDatePicker(options)
| Option | Type |
|---|---|
value | Date | null |
onChange | (date?: Date) => void |
onBlur | () => void |
minValue / maxValue | Date |
isDateUnavailable | (date: Date) => boolean |
disabled | boolean |
isInvalid / aria-invalid | boolean |
Returns { state: { hasValue, isInvalid }, ariaProps }. Spread ariaProps onto <AriaDatePicker> from react-aria-components.
For accessibility details on the underlying primitives, see React Aria DatePicker.