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 any date segmentOpen popover
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)Commit date, keep popover open for time
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

Enables the VCP time row via withTime. The popover stays open after day selection until you finish time or dismiss.

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

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 { 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:

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

API Reference

Props

PropTypeDefault
valueDate | null
onChange(date: Date | undefined) => void
onBlur() => void
localestringi18n/provider locale
withTimebooleanfalse
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