# Date Picker

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

> For the complete documentation index, see [llms.txt](/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](/.well-known/agent-skills/site-skill.md).





## Playground [#playground]

```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 [#installation]

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

```bash
npx 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 [#dependencies]

Installed automatically by the registry CLI. For manual setup:

```bash
npm install date-fns lucide-react
```

## Usage [#usage]

```tsx
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 [#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) [#storybook-stories-optional]

Install DatePicker first, then:

```bash
npx 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 [#examples]

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

<ComponentPreview name="date-picker-demo" title="date-picker-demo.tsx">
  <DatePickerDemo />
</ComponentPreview>

## Behavior & Accessibility [#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 [#props]

| Prop          | Type                              | Default         | Description                                                                       |
| ------------- | --------------------------------- | --------------- | --------------------------------------------------------------------------------- |
| motion        | boolean                           | true            | Enables popup, calendar, and button motion; system reduced-motion takes priority. |
| className     | string                            | undefined       | DatePicker: trigger class name.                                                   |
| clearable     | boolean                           | false           | DatePicker: shows a clear action for a selected value.                            |
| dateFormat    | string                            | "PPP"           | DatePicker: date-fns display format.                                              |
| defaultOpen   | boolean                           | false           | DatePicker: initial uncontrolled popover visibility.                              |
| defaultValue  | Date \| undefined                 | undefined       | DatePicker: initial uncontrolled selected day.                                    |
| disabled      | boolean                           | false           | DatePicker: disables opening the trigger.                                         |
| disabledDates | Matcher \| Matcher\[]             | undefined       | DatePicker: dates that cannot be selected.                                        |
| endMonth      | Date                              | undefined       | DatePicker: latest navigable month.                                               |
| id            | string                            | generated       | DatePicker: optional stable id for the label relationship.                        |
| label         | string                            | required        | DatePicker: visible accessible name.                                              |
| locale        | date-fns Locale                   | undefined       | DatePicker: display and calendar locale.                                          |
| onOpenChange  | (open: boolean) => void           | undefined       | DatePicker: observes popover visibility changes.                                  |
| onValueChange | (date: Date \| undefined) => void | undefined       | DatePicker: fires immediately when selected or cleared.                           |
| open          | boolean                           | uncontrolled    | DatePicker: controlled popover visibility.                                        |
| placeholder   | string                            | "Select a date" | DatePicker: trigger text when empty.                                              |
| readOnly      | boolean                           | false           | DatePicker: opens the calendar but prevents changes.                              |
| startMonth    | Date                              | undefined       | DatePicker: earliest navigable month.                                             |
| value         | Date \| undefined                 | uncontrolled    | DatePicker: controlled selected local calendar day.                               |

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 [#source]

```tsx
"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 [#credits]

* [shadcn/ui](https://ui.shadcn.com/docs/components/base/date-picker): Button, Popover, and Calendar composition pattern; Vandor adds clear/read-only behavior and motion controls.
