One DatePicker component that handles single dates, ranges, natural-language input, and paired time pickers.
Try “next monday”, “2 fridays from now”, or “last day of next month”.
npx shadcn@latest add https://sdk-components.thesqd.com/r/date-picker.jsonimport { DatePicker } from "@/components/ui/date-picker";"use client";
import { DatePicker } from "@/components/ui/date-picker";
export function Example() {
return <DatePicker mode="single" label="Pick a date" />;
}DatePicker (master component — pick a mode)
├── mode="single" (natural-language input by default)
│ ├── naturalLanguage={false} (swap to a plain outline trigger)
│ ├── withTime (adds a paired native time input)
│ └── withTime + naturalLanguage (parses "tomorrow at 3pm" into both)
├── mode="range" (start + end date in one calendar)
│ ├── withTime (adds paired start/end time inputs)
│ └── variant="inline" (pencil-edit pill → two separate date fields)
└── mode="weekly" (no calendar — weekday toggles + times per day)
└── each day → RepeatableInput of inline hour/minute/AM-PM time fields
Internally composes Popover + Button + Calendar; you don't wire them yourself.Past dates are disabled.
Blocked dates
Try “next monday”, “2 fridays from now”, or “last day of next month”.
Try “tomorrow at 3pm” or “next monday 10:30am”.
Pick every day you meet, then add each service time.
Please pick a date.
Common
| Prop | Type | Default | Description |
|---|---|---|---|
mode | "single" | "range" | "single" | Selection model. |
label | ReactNode | — | Field label rendered above the trigger. |
placeholder | string | "Pick a date" | Trigger text shown when no date is selected. |
caption | ReactNode | — | Caption rendered beneath the trigger. Hidden when `error` is set. |
error | ReactNode | — | Inline error message — swaps the helper line for the red treatment and applies the red focus/border ring on the trigger. |
disabled | Matcher | Matcher[] | — | Disable specific days. e.g. `{ before: today }` for future-only. |
blockedDates | Date[] | — | Convenience array of individual days to black out (past or future). Merges with `disabled`. Free-text input ignores blocked dates. |
minDate | Date | — | Earliest selectable day (inclusive). Days before it are disabled. Merges with `disabled`/`blockedDates`. |
maxDate | Date | — | Latest selectable day (inclusive). Days after it are disabled. Merges with `disabled`/`blockedDates`. |
align | "start" | "center" | "end" | "start" | Popover alignment relative to the trigger. |
withIcon | boolean | true | Show the calendar icon inside the trigger. |
mode="single"
| Prop | Type | Default | Description |
|---|---|---|---|
value | Date | undefined | — | Controlled selected date. |
onChange | (date: Date | undefined) => void | — | Fires when the user picks a day (or a day + time). |
withTime | boolean | false | Render a paired native time input next to the date trigger. |
defaultTime | string | "06:30" | Initial 24-hour time when `withTime` is enabled. |
naturalLanguage | boolean | true | Free-text input that parses phrases via chrono-node. Pass `false` for a plain trigger. |
quickSelections | QuickSelection[] | — | Radio-style chips below the calendar (and time row). Built-in presets: `"today"`, `"tomorrow"`, `"yesterday"`, `"next-week"`, `"next-month"`. Or pass custom `{ label, getDate: (now) => Date }` entries. "Today" resolves against the user's current local day. Omit to hide the row. |
mode="range"
| Prop | Type | Default | Description |
|---|---|---|---|
value | DateRange | undefined | — | { from: Date; to?: Date } controlled range. |
onChange | (range: DateRange | undefined) => void | — | Fires after each pick. The second pick completes the range. |
withTime | boolean | false | Render paired start/end time inputs in the popover. `onChange` returns the range with each time applied to `from`/`to`. |
defaultTime | string | "09:00" | Initial 24-hour start time when `withTime` is enabled. |
defaultEndTime | string | "17:00" | Initial 24-hour end time when `withTime` is enabled. |
variant | "field" | "inline" | "field" | `"inline"` renders a compact pencil-edit pill (the Duration / File size input feel) that expands into two separate single-date pickers for Start and End — each its own regular calendar popover, not a shared range calendar. The End field can't go before Start (and vice-versa); a backwards range typed via natural language surfaces an inline error and blocks collapse. Respects `minDate`/`maxDate`/`blockedDates`. Works with or without `withTime`. |
quickSelections | RangeQuickSelection[] | — | Radio-style chips below the calendar that apply a preset *range* (both endpoints). Built-in presets: `"today"`, `"yesterday"`, `"last-7-days"`, `"last-30-days"`, `"this-week"`, `"this-month"`, `"last-month"`. Or pass custom `{ label, getRange: (now) => DateRange }` entries. Omit to hide the row. |
mode="weekly"
| Prop | Type | Default | Description |
|---|---|---|---|
value | WeeklySchedule | — | Controlled schedule — `Partial<Record<Weekday, string[]>>`, e.g. `{ sun: ["09:00", "11:00"] }`. A weekday key is present only while that day is selected. |
defaultValue | WeeklySchedule | — | Initial schedule when uncontrolled. |
onChange | (schedule: WeeklySchedule) => void | — | Fires on every day toggle, time edit, add, and remove. |
defaultTime | string | "" | 24-hour time prefilled on each new row. Empty renders the `-- : -- --` placeholder segments. |
days | Weekday[] | ["sun", "mon", "tue", "wed", "thu", "fri", "sat"] | Which weekdays are offered, in render order. |
maxTimesPerDay | number | — | Cap on times per day. Each day’s + button disables once it hits the cap. Unlimited when omitted. |