f-ui
Components

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

EventBehavior
Calendar trigger clickToggle popover
Focus / Tab into date segmentsDoes not open popover (type to edit; open via calendar icon)
Edit segments while popover is openPopover stays open; calendar month and selection sync to the typed date
Select day (no time)Commit date, close popover
Select day (withTime)Update draft date; popover stays open for time
Scroll / click time spinnerUpdate draft time; selected date preserved
Edit time segments in fieldUpdate value (or draft while popover is open)
Click Done (withTime)Commit and close popover
Click Today (withTime)Set draft to today (keeps current time when already set)
Outside click / EscapeClose popover (withTime commits draft on close)

Installing

pnpm dlx shadcn@latest add https://ui.isaacfei.com/r/date-picker.json
npx shadcn@latest add https://ui.isaacfei.com/r/date-picker.json
yarn dlx shadcn@latest add https://ui.isaacfei.com/r/date-picker.json
bun x shadcn@latest add https://ui.isaacfei.com/r/date-picker.json

Or 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.

mmddyyyy

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

When withTime is set, a compact scroll time spinner appears below the calendar (about three visible rows); picking a date preserves the chosen time. Choose a date on the calendar, set the time in the spinner below (or type in the field), then click Done — or click outside to apply and close.

For a taller spinner, pass timeOptions={{ density: "default" }}. In tight panels, combine with calendarPopover={{ side: "top" }}.

mmddyyyy––––AM

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.

mmddyyyy

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. The calendar popover defaults above the dialog layer (z-[60] over Dialog z-50). Opening the dialog focuses the field for typing without auto-opening the calendar — use the calendar icon to pick.

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
          />
        </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

useDatePicker is the view-model: open/close, fieldProps, and calendarProps. The demo uses a native <input> + <button> for field chrome — not DatePickerControl or DatePickerTrigger. Wire calendarProps into Calendar inside DatePickerPopover.

Committed value:

"use client";

import { useState } from "react";

import { Calendar } from "@/components/f-ui/calendar/calendar";
import { DatePickerPopover } from "@/components/f-ui/date-picker/date-picker-parts/popover";
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 } 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) : "—";
  const fieldDisplay = date ? date.toLocaleDateString(locale) : "";

  return (
    <Popover open={picker.isOpen} onOpenChange={picker.onOpenChange}>
      <div>
        <label htmlFor="headless-date-picker">Date</label>
        <PopoverAnchor asChild>
          <div id="headless-date-picker">
            <input
              type="text"
              readOnly
              value={fieldDisplay}
              onFocus={picker.fieldProps.onFocus}
              onBlur={picker.fieldProps.onBlur}
              aria-label={t("ariaLabel")}
            />
            <button type="button" onClick={picker.onTriggerClick} aria-label={t("openCalendar")}>
              Calendar
            </button>
          </div>
        </PopoverAnchor>
        <p>Committed value: {committedLabel}</p>
      </div>
      <DatePickerPopover>
        <Calendar {...picker.calendarProps} showToday />
      </DatePickerPopover>
    </Popover>
  );
}

You can also compose from lower-level pieces without the hook — manage open state yourself and close on day pick:

mmddyyyy

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}
              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 message

API Reference

Props

PropTypeDefaultDescription
valueDate | null
onChange(date: Date | undefined) => void
onBlur() => void
localestringi18n/provider locale
withTimebooleanfalse
timeOptions{ format?: string; use12Hours?: boolean; step?: { hour?; minute?; second? }; density?: "default" | "compact" }Configures the in-popover time spinner when withTime is set. density defaults to "compact" in the calendar popover; pass "default" for the full standalone TimePicker height. Also supports 12-hour (use12Hours), seconds (format: "HH:mm:ss"), and stepped columns (step).
minValue / maxValueDate
isDateUnavailable(date: Date) => boolean
firstDayOfWeek0 | 1 | 2 | 3 | 4 | 5 | 6locale default
disabledbooleanfalse
isInvalidbooleanfalse
labelReactNode
aria-labelstringlocalized ariaLabel
descriptionReactNode
errorMessageReactNode
showTodaybooleancalendar default
classNamestring
classNamesPartial<Record<DatePickerSlot, string>>
tDatePickerTranslateFnbuilt-in/provider

Slots

SlotElement
rootOuter wrapper
fieldField wrapper
inputSegmented date input inside control
iconCalendar trigger button
popoverPopover panel
calendarEmbedded Calendar
descriptionDescription text
errorMessageError text

Hook — useDatePicker(options)

OptionType
valueDate | null
onChange(date: Date | undefined) => void
onBlur() => void
localestring (required)
withTimeboolean
minValue / maxValueDate
isDateUnavailable(date: Date) => boolean
firstDayOfWeek0–6
disabledboolean
isInvalidboolean
ReturnType
valuecommitted Date
isOpen / setIsOpenpopover open state
onOpenChange(open: boolean) => void
onTriggerClicktoggle handler for your trigger button
fieldPropsspread on DatePickerControl (omit calendarTrigger)
calendarPropsspread on Calendar (mode: "single")

On this page