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”.
Installation
$Terminal
npx shadcn@latest add https://sdk-components.thesqd.com/r/date-picker.jsonUsage
Render `<DatePicker mode="single" />` or `<DatePicker mode="range" />`. Add `withTime` for a paired time input or `naturalLanguage` for free-text parsing.
TSImport
import { DatePicker } from "@/components/ui/date-picker";TSExample
"use client";
import { DatePicker } from "@/components/ui/date-picker";
export function Example() {
return <DatePicker mode="single" label="Pick a date" />;
}Composition
Anatomy of a date-picker compound.
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)
Internally composes Popover + Button + Calendar; you don't wire them yourself.Clear button
Every variant — single, range, natural-language input, and date + time — renders a `×` ghost Button inside the trigger when a value is selected. Click it to clear the date (and the input text where applicable) without re-opening the calendar. Same hover treatment as the chevron sitting next to it.
Date picker
Plain date picker — an outline trigger that opens the calendar in a popover.
Future dates only
Disables every day before today so the user can only pick an upcoming date.
Past dates are disabled.
Blacked-out dates
Pass `blockedDates={[…]}` an array of `Date` objects to disable specific days on the calendar — handy for company holidays, already-booked slots, or any non-contiguous blackout.
Blocked dates
- · Mon, May 4, 2026
- · Mon, May 11, 2026
- · Thu, May 14, 2026
- · Tue, May 19, 2026
- · Wed, May 27, 2026
Range date picker
Select a start and end date in a single calendar.
Range date + time picker
Add `withTime` to `mode="range"` for a start and end time alongside the date range. `onChange` returns a `DateRange` with the times applied to `from`/`to`.
Inline range picker
Pass `variant="inline"` for a compact pill with a pencil edit affordance — the same display/edit feel as the Duration and File size inputs. Clicking expands it into two separate single-date pickers (Start and End), each its own regular calendar popover, with a ✓ to collapse back.
Inline range + time picker
Combine `variant="inline"` with `withTime` so each endpoint expands into its own date + time popover. `onChange` returns a `DateRange` whose `from`/`to` carry the selected times.
Natural language date picker
Parses phrases like “next monday” or “in 2 weeks”.
Try “next monday”, “2 fridays from now”, or “last day of next month”.
Date + time picker
Combined calendar and time input, side by side.
Inline date + time picker
`variant="inline"` renders a plain-text trigger (no field chrome) that opens the same calendar + time popover on click — for tables and inline-edit cells.
Natural language date + time picker
Free-text input that parses phrases like “tomorrow at 3pm” into both a date and a time, alongside the native time field.
Try “tomorrow at 3pm” or “next monday 10:30am”.
Quick selection chips
Pass `quickSelections` an array of presets (`"today"`, `"tomorrow"`, `"yesterday"`, `"next-week"`, `"next-month"`) and/or custom `{ label, getDate }` entries to render radio-style chips below the calendar (and time row). “Today” resolves against the user's current local day. Omit the prop entirely to hide the row.
Quick selection chips (range)
On `mode="range"`, `quickSelections` takes preset ranges (`"last-7-days"`, `"last-30-days"`, `"this-week"`, `"this-month"`, `"last-month"`, …) or custom `{ label, getRange }` entries that fill both endpoints in one click.
Error state
Pass `error="…"` to swap the helper text for the inline red message and apply the red focus/border treatment to the trigger.
Please pick a date.
API Reference
Props for the DatePicker component.
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. |