For the complete documentation index, see llms.txt. Markdown variants are available by appending .md to any URL or sending an Accept: text/markdown header. An agent skill is available at /.well-known/agent-skills/site-skill.md.
1
Support

Date Picker

A single-date picker combining an accessible popover and calendar.

Playground

Live preview
Customize date-picker
Customize
date-picker-demo.tsx
"use client";

import { useState } from "react";
import { DatePicker } from "@/components/ui/date-picker";

export function DatePickerDemo() {
  const [date, setDate] = useState<Date>();
  return (
    <>
      <DatePicker
          label={"Appointment date"}
          value={date}
          onValueChange={setDate}
          placeholder={"Select a date"}
          dateFormat={"PPP"}
          motion={true}
      />
    </>
  );
}

Installation

Initialize shadcn in your React project, then install this component from the Vandor UI registry:

$ pnpm dlx shadcn@latest add https://vandor-ui.vercel.app/r/date-picker.json

The default installation target is components/ui/date-picker.tsx. Review the CLI's overwrite prompt before replacing local customizations.

Dependencies

Installed automatically by the registry CLI. For manual setup:

Dependencies2 required packages
  • date-fns
  • lucide-react
$ pnpm add date-fns lucide-react

Usage

import { DatePicker } from "@/components/ui/date-picker";
 
<DatePicker label="Appointment date" value={date} onValueChange={setDate} />;

The value is a JavaScript Date representing a local calendar day. Set clearable to include an explicit clear action.

This follows shadcn's Date Picker composition, not a separate upstream DatePicker registry component. It uses Vandor's new animated Popover. motion={false} disables popup, Calendar, and button animations; system reduced-motion preferences take priority. Popup reveal takes 180ms and dismissal 120ms. There is no delayed commit.

Consumer setup

Requires React 19.2+, Tailwind CSS 4 with shadcn theme tokens, and the declared dependencies. CLI installation includes Button (and its Loading files), Calendar, and Popover; review existing primitive overwrite prompts. Copying Source alone is insufficient for manual installation: install these registry dependencies too. No additional animation stylesheet is needed.

Storybook stories (optional)

Install DatePicker first, then:

$ pnpm dlx shadcn@latest add https://vandor-ui.vercel.app/r/date-picker-stories.json

Only the stories file is installed; no component or Storybook configuration is overwritten. Add your UI directory to your existing **/*.stories.@(js|jsx|mjs|ts|tsx) discovery pattern and import your app's global Tailwind CSS in Storybook preview. Stories use @storybook/react 10.6.1 types/composition; keep Storybook packages aligned. autodocs needs your existing docs addon. Stories inherit consumer themes.

Examples

This controlled picker exposes a clear action and commits each selected local calendar day immediately.

date-picker-demo.tsx
"use client";

import { useState } from "react";

import { DatePicker } from "@/components/ui/date-picker";

export const DatePickerDemo = () => {
  const [date, setDate] = useState<Date>();
  return (
    <DatePicker
      label="Appointment date"
      value={date}
      onValueChange={setDate}
      clearable
    />
  );
};

Behavior & Accessibility

Selecting an allowed day commits immediately and closes the popup. Escape dismisses and returns focus to the trigger. disabledDates and month-navigation bounds are separate controls. Display formatting does not change the underlying date.

Opening starts on the selected date's month and focuses an eligible date. The popup has an accessible title; the trigger's name includes its label and displayed value. Read-only permits inspection/navigation but disables selection and clearing. Disabled prevents trigger interaction. value (including explicit undefined) is controlled; otherwise defaultValue initializes internal state. open/onOpenChange independently control visibility.

This is not a native form input: serialize your date explicitly. Dates use local calendar days; avoid converting local midnight to UTC without a deliberate timezone policy. The public API is restricted to the listed props, not arbitrary native input attributes. Playground Reset clears selection and closes the popup; generated code reproduces configuration rather than transient selection.

Props

Component props, types, defaults, and descriptions
PropType / DescriptionDefault
motionboolean

Enables popup, calendar, and button motion; system reduced-motion takes priority.

true
classNamestring

DatePicker: trigger class name.

undefined
clearableboolean

DatePicker: shows a clear action for a selected value.

false
dateFormatstring

DatePicker: date-fns display format.

"PPP"
defaultOpenboolean

DatePicker: initial uncontrolled popover visibility.

false
defaultValueDate | undefined

DatePicker: initial uncontrolled selected day.

undefined
disabledboolean

DatePicker: disables opening the trigger.

false
disabledDatesMatcher | Matcher[]

DatePicker: dates that cannot be selected.

undefined
endMonthDate

DatePicker: latest navigable month.

undefined
idstring

DatePicker: optional stable id for the label relationship.

generated
labelstring

DatePicker: visible accessible name.

required
localedate-fns Locale

DatePicker: display and calendar locale.

undefined
onOpenChange(open: boolean) => void

DatePicker: observes popover visibility changes.

undefined
onValueChange(date: Date | undefined) => void

DatePicker: fires immediately when selected or cleared.

undefined
openboolean

DatePicker: controlled popover visibility.

uncontrolled
placeholderstring

DatePicker: trigger text when empty.

"Select a date"
readOnlyboolean

DatePicker: opens the calendar but prevents changes.

false
startMonthDate

DatePicker: earliest navigable month.

undefined
valueDate | undefined

DatePicker: controlled selected local calendar day.

uncontrolled

The reference lists component-specific props and selected native props, not the complete React API. Other native attributes are forwarded to the rendered element.

Source

"use client";
 
import { cn } from "cn";
import { format } from "date-fns";
import type { Locale } from "date-fns";
import { CalendarDays } from "lucide-react";
import * as React from "react";
import type { Matcher } from "react-day-picker";
 
import { Button } from "./button";
import { Calendar } from "./calendar";
import {
  Popover,
  PopoverContent,
  PopoverTitle,
  PopoverTrigger,
} from "./popover";
 
// Composes shadcn's Date Picker pattern with Vandor's Button, Popover and Calendar.
 
interface CalendarOptions {
  disabledDates?: Matcher | Matcher[];
  startMonth?: Date;
  endMonth?: Date;
}
 
export type DatePickerProps = CalendarOptions & {
  id?: string;
  label: string;
  placeholder?: string;
  clearable?: boolean;
  value?: Date;
  defaultValue?: Date;
  onValueChange?: (date: Date | undefined) => void;
  locale?: Locale;
  dateFormat?: string;
  disabled?: boolean;
  readOnly?: boolean;
  open?: boolean;
  defaultOpen?: boolean;
  onOpenChange?: (open: boolean) => void;
  className?: string;
  motion?: boolean;
};
 
export const DatePicker = (props: DatePickerProps) => {
  const {
    id,
    label,
    placeholder = "Select a date",
    clearable = false,
    value,
    defaultValue,
    onValueChange,
    locale,
    dateFormat = "PPP",
    disabled,
    readOnly,
    open,
    defaultOpen = false,
    onOpenChange,
    className,
    motion = true,
    ...calendarOptions
  } = props;
  const controlled = Object.hasOwn(props, "value");
  const generatedId = React.useId();
  const triggerId = id ?? generatedId;
  const [internal, setInternal] = React.useState(defaultValue);
  const [internalOpen, setInternalOpen] = React.useState(defaultOpen);
  const current = controlled ? value : internal;
  const isOpen = open === undefined ? internalOpen : open;
  const setOpen = (next: boolean) => {
    if (open === undefined) {
      setInternalOpen(next);
    }
    onOpenChange?.(next);
  };
  const selectDate = (date?: Date) => {
    if (!controlled) {
      setInternal(date);
    }
    onValueChange?.(date);
  };
 
  return (
    <div data-slot="date-picker" className="grid min-w-0 gap-1.5">
      <label
        htmlFor={triggerId}
        id={`${triggerId}-label`}
        className="text-sm font-medium"
      >
        {label}
      </label>
      <Popover open={isOpen} onOpenChange={setOpen}>
        <PopoverTrigger
          id={triggerId}
          disabled={disabled}
          aria-labelledby={`${triggerId}-label ${triggerId}-value`}
          render={
            <Button
              variant="outline"
              whileTap={motion ? { scale: 0.96 } : false}
            />
          }
          className={cn(
            "inline-flex h-9 min-w-0 items-center justify-start gap-2 rounded-md border border-input bg-background px-3 text-sm font-normal shadow-xs outline-none hover:bg-accent focus-visible:border-ring focus-visible:ring-[3px] focus-visible:ring-ring/50 disabled:pointer-events-none disabled:opacity-50 dark:bg-input/30",
            className
          )}
        >
          <CalendarDays aria-hidden="true" className="size-4 shrink-0" />
          <span
            id={`${triggerId}-value`}
            className={cn("truncate", !current && "text-muted-foreground")}
          >
            {current ? format(current, dateFormat, { locale }) : placeholder}
          </span>
        </PopoverTrigger>
        <PopoverContent
          align="start"
          animated={motion}
          className="w-auto gap-0 p-0"
        >
          <PopoverTitle className="sr-only">{label}</PopoverTitle>
          <Calendar
            mode="single"
            motion={motion}
            autoFocus
            defaultMonth={current}
            locale={locale}
            selected={current}
            disabled={readOnly ? () => true : calendarOptions.disabledDates}
            startMonth={calendarOptions.startMonth}
            endMonth={calendarOptions.endMonth}
            onSelect={(date) => {
              selectDate(date);
              setOpen(false);
            }}
          />
          {clearable && current && !readOnly ? (
            <Button
              type="button"
              variant="ghost"
              whileTap={motion ? { scale: 0.96 } : false}
              className="m-2 mt-0 w-auto"
              onClick={() => {
                selectDate();
                setOpen(false);
              }}
            >
              Clear date
            </Button>
          ) : null}
        </PopoverContent>
      </Popover>
    </div>
  );
};
 

Credits

  • shadcn/ui: Button, Popover, and Calendar composition pattern; Vandor adds clear/read-only behavior and motion controls.