- Accordion
- AppShell
- Autocomplete
- Badge
- Blobatar
- BoringAvatar
- Button
- Calendar
- Checkbox
- DataGrid
- Date Picker
- Date Range Picker
- DateTime
- Dialog
- Drawer
- Dropdown
- Empty
- Error State
- Frame
- Input
- Input Amount
- Input Group
- Input OTP
- Input Password
- Input Phone
- Input Search
- Input Secret
- Loading
- PageContainer
- PageHeader
- Popover
- Select
- StatsCard
- Switch
- Tabs
- TextArea
- Toast
- Tooltip
Playground
"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:
- date-fns
- lucide-react
$ pnpm add date-fns lucide-reactUsage
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.
"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
| Prop | Type / Description | Default |
|---|---|---|
| motion | booleanEnables popup, calendar, and button motion; system reduced-motion takes priority. | true |
| className | stringDatePicker: trigger class name. | undefined |
| clearable | booleanDatePicker: shows a clear action for a selected value. | false |
| dateFormat | stringDatePicker: date-fns display format. | "PPP" |
| defaultOpen | booleanDatePicker: initial uncontrolled popover visibility. | false |
| defaultValue | Date | undefinedDatePicker: initial uncontrolled selected day. | undefined |
| disabled | booleanDatePicker: disables opening the trigger. | false |
| disabledDates | Matcher | Matcher[]DatePicker: dates that cannot be selected. | undefined |
| endMonth | DateDatePicker: latest navigable month. | undefined |
| id | stringDatePicker: optional stable id for the label relationship. | generated |
| label | stringDatePicker: visible accessible name. | required |
| locale | date-fns LocaleDatePicker: display and calendar locale. | undefined |
| onOpenChange | (open: boolean) => voidDatePicker: observes popover visibility changes. | undefined |
| onValueChange | (date: Date | undefined) => voidDatePicker: fires immediately when selected or cleared. | undefined |
| open | booleanDatePicker: controlled popover visibility. | uncontrolled |
| placeholder | stringDatePicker: trigger text when empty. | "Select a date" |
| readOnly | booleanDatePicker: opens the calendar but prevents changes. | false |
| startMonth | DateDatePicker: earliest navigable month. | undefined |
| value | Date | undefinedDatePicker: 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.