npm install @thrivecart/uiyarn add @thrivecart/uipnpm add @thrivecart/uibun add @thrivecart/uiimport { DatePicker } from '@thrivecart/ui';Select a single date using the default button trigger
function BasicDatePicker() {
const [date, setDate] = React.useState();
return (
<div className="w-72">
<Label className="mb-1 block">Select Date</Label>
<DatePicker
value={date}
onChange={setDate}
placeholder="Pick a date"
/>
</div>
);
}Use triggerType to switch between a button-style and an input-style trigger
<div className="flex flex-col gap-6 w-72">
<div>
<Label className="mb-1 block">Button Trigger (Default)</Label>
<DatePicker
value={date1}
onChange={setDate1}
placeholder="Pick a date"
triggerType="button"
/>
</div>
<div>
<Label className="mb-1 block">Input Trigger</Label>
<DatePicker
value={date2}
onChange={setDate2}
placeholder="Pick a date"
triggerType="input"
/>
</div>
</div>Built-in presets:
Combine isRange and hasPresets for a full reporting-period picker
<div className="w-96">
<Label className="mb-1 block">Report Period</Label>
<DatePicker
value={range}
onChange={setRange}
placeholder="Select period"
isRange
hasPresets
/>
</div>Pass a presets array to replace the default options with your own
const customPresets = [
{
label: 'Last 7 Days',
value: () => {
const end = new Date();
const start = new Date();
start.setDate(start.getDate() - 6);
start.setHours(0, 0, 0, 0);
end.setHours(23, 59, 59, 999);
return [start, end];
},
},
{
label: 'Last 30 Days',
value: () => {
const end = new Date();
const start = new Date();
start.setDate(start.getDate() - 29);
start.setHours(0, 0, 0, 0);
end.setHours(23, 59, 59, 999);
return [start, end];
},
},
{
label: 'Last 90 Days',
value: () => {
const end = new Date();
const start = new Date();
start.setDate(start.getDate() - 89);
start.setHours(0, 0, 0, 0);
end.setHours(23, 59, 59, 999);
return [start, end];
},
},
{
label: 'Last 12 Months',
value: () => {
const end = new Date();
const start = new Date();
start.setFullYear(start.getFullYear() - 1);
start.setHours(0, 0, 0, 0);
end.setHours(23, 59, 59, 999);
return [start, end];
},
},
{
label: 'Year to Date',
value: () => {
const end = new Date();
const start = new Date(end.getFullYear(), 0, 1);
start.setHours(0, 0, 0, 0);
end.setHours(23, 59, 59, 999);
return [start, end];
},
},
{ label: 'Custom', value: () => new Date() },
];
<DatePicker
value={range}
onChange={setRange}
placeholder="Select period"
isRange
hasPresets
presets={customPresets}
/>Use disabledDays to prevent selection of invalid dates and endMonth to cap the visible range
Only future dates within the next month are available
function AppointmentPicker() {
const [date, setDate] = React.useState();
const today = new Date();
today.setHours(0, 0, 0, 0);
const nextMonth = new Date(today.getFullYear(), today.getMonth() + 1, today.getDate());
return (
<div className="w-72">
<Label className="mb-1 block">Appointment Date</Label>
<DatePicker
value={date}
onChange={setDate}
placeholder="Select appointment"
disabledDays={(date) => date < today}
endMonth={nextMonth}
/>
<p className="text-xs text-muted-foreground mt-1">
Only future dates within the next month are available
</p>
</div>
);
}Control how selected dates are displayed using dateFormat (date-fns format strings)
const [date, setDate] = React.useState(new Date());
<div className="flex flex-col gap-4 w-72">
<div>
<Label className="mb-1 block">Default (MMM dd, yyyy)</Label>
<DatePicker value={date} onChange={setDate} dateFormat="MMM dd, yyyy" />
</div>
<div>
<Label className="mb-1 block">Short (MM/dd/yyyy)</Label>
<DatePicker value={date} onChange={setDate} dateFormat="MM/dd/yyyy" />
</div>
<div>
<Label className="mb-1 block">Long (EEEE, MMMM do, yyyy)</Label>
<DatePicker value={date} onChange={setDate} dateFormat="EEEE, MMMM do, yyyy" />
</div>
</div>Use captionLayout to replace the static month label with clickable dropdowns. Requires startMonth and endMonth to define the navigable range.
const startMonth = new Date(2020, 0, 1);
const endMonth = new Date(2030, 11, 31);
// Month + year dropdowns
<DatePicker
value={date}
onChange={setDate}
placeholder="Pick a date"
captionLayout="dropdown"
startMonth={startMonth}
endMonth={endMonth}
/>
// Month dropdown only
<DatePicker
captionLayout="dropdown-months"
startMonth={startMonth}
endMonth={endMonth}
/>
// Year dropdown only
<DatePicker
captionLayout="dropdown-years"
startMonth={startMonth}
endMonth={endMonth}
/>captionLayout options:
| Value | Description |
|---|---|
label | Static "Month Year" label (default) |
dropdown | Both month and year dropdowns |
dropdown-months | Month dropdown, static year label |
dropdown-years | Year dropdown, static month label |
buttons | Navigation arrows only |
Pass captionLayout directly to the Calendar component
| Su | Mo | Tu | We | Th | Fr | Sa |
|---|---|---|---|---|---|---|
<Calendar
mode="single"
selected={date}
onSelect={setDate}
captionLayout="dropdown"
startMonth={new Date(2020, 0, 1)}
endMonth={new Date(2030, 11, 31)}
/>Add showTime to display an hour / minute / AM-PM picker below the calendar. The time is merged into the date value on Apply.
function DateTimePicker() {
const [date, setDate] = React.useState();
return (
<div className="w-72">
<Label className="mb-1 block">Date & Time</Label>
<DatePicker
value={date}
onChange={setDate}
placeholder="Pick date and time"
showTime
/>
</div>
);
}Combine isRange and showTime for a range picker that also captures a time component
<DatePicker
value={range}
onChange={setRange}
placeholder="Select range with time"
isRange
showTime
/>All three features together: month/year dropdowns, date range, and time picker
<DatePicker
value={date}
onChange={setDate}
placeholder="Pick date and time"
captionLayout="dropdown"
startMonth={new Date(2020, 0, 1)}
endMonth={new Date(2030, 11, 31)}
showTime
/>The disabled prop prevents interaction on both trigger types
<div className="flex flex-col gap-4 w-72">
<div>
<Label className="mb-1 block">Disabled (button trigger)</Label>
<DatePicker value={new Date()} disabled placeholder="Cannot change" />
</div>
<div>
<Label className="mb-1 block">Disabled (input trigger)</Label>
<DatePicker value={new Date()} disabled triggerType="input" placeholder="Cannot change" />
</div>
</div>DatePicker inside a Field component with label and helper text
function EventForm() {
const [eventData, setEventData] = React.useState({
title: '',
dateRange: undefined,
});
return (
<form className="w-96 p-6 border border-border rounded-lg space-y-4">
<h3 className="text-lg font-semibold">Schedule Event</h3>
<Field label="Event Title" required>
<Input
value={eventData.title}
onChange={(e) =>
setEventData(prev => ({ ...prev, title: e.target.value }))
}
placeholder="Team Offsite Meeting"
/>
</Field>
<Field label="Event Dates" required helperText="Select start and end dates">
<DatePicker
value={eventData.dateRange}
onChange={(d) =>
setEventData(prev => ({ ...prev, dateRange: d }))
}
placeholder="Select date range"
isRange
hasPresets
/>
</Field>
<Button type="submit" className="w-full">Schedule Event</Button>
</form>
);
}The MonthPicker component shows a year navigation header and a 3×4 grid of months. Use it when the user needs to select a month and year without a specific day.
Select a month from the 3×4 grid; navigate years with the arrows
function BasicMonthPicker() {
const [value, setValue] = React.useState(new Date());
return (
<MonthPicker value={value} onChange={setValue} />
);
}Use minYear and maxYear to restrict navigation range
<MonthPicker
value={value}
onChange={setValue}
minYear={2020}
maxYear={2030}
/>| Prop | Type | Default | Description |
|---|---|---|---|
value | Date | — | Currently selected date (month + year only) |
onChange | (date: Date) => void | — | Called with a Date set to the 1st of the selected month |
minYear | number | — | Minimum navigable year |
maxYear | number | — | Maximum navigable year |
className | string | — | Additional className |
The Calendar component can be used standalone for embedded date selection without a popover.
Embedded calendar for single date selection
| Su | Mo | Tu | We | Th | Fr | Sa |
|---|---|---|---|---|---|---|
function StandaloneCalendar() {
const [date, setDate] = React.useState(new Date());
return (
<Calendar
mode="single"
selected={date}
onSelect={setDate}
/>
);
}Embedded calendar showing two months side-by-side for range selection
| Su | Mo | Tu | We | Th | Fr | Sa |
|---|---|---|---|---|---|---|
| Su | Mo | Tu | We | Th | Fr | Sa |
|---|---|---|---|---|---|---|
function RangeCalendar() {
const [range, setRange] = React.useState({});
return (
<Calendar
mode="range"
selected={range}
onSelect={(r) => setRange(r ?? {})}
numberOfMonths={2}
/>
);
}Use hasPresets when users frequently select common ranges like "This Week" or "Last Month". Define custom presets for domain-specific periods like "Last 30 Days" or "Year to Date".
Disable dates that are not valid selections — past dates for scheduling, future dates for historical reports, weekends for business-only selections.
Use input trigger (triggerType="input") in forms alongside other inputs for visual consistency. Use button trigger for standalone or toolbar date selections.
Use descriptive placeholders that indicate what date is expected, e.g. "Select report period" or "Choose appointment date".
For date ranges, ensure end date cannot be before start date. The component handles this automatically when using isRange.
Users should always see the selected date in a clear, readable format. Avoid overly technical format strings like ISO 8601 in the display value.
| Prop | Type | Default | Description |
|---|---|---|---|
value | Date | [Date, Date] | — | Selected date or date range |
onChange | (date: Date | [Date, Date] | undefined) => void | — | Called when selection is applied |
placeholder | string | 'Select date' | Trigger placeholder text |
isRange | boolean | false | Enable date range selection |
hasPresets | boolean | false | Show preset options sidebar |
presets | DatePreset[] | built-in | Custom preset definitions; replaces defaults |
triggerType | 'button' | 'input' | 'button' | Trigger visual style |
disabled | boolean | false | Disable the picker entirely |
dateFormat | string | 'MMM dd, yyyy' | date-fns format string for display |
disabledDays | (date: Date) => boolean | — | Return true to disable a specific date |
startMonth | Date | — | Earliest month that can be navigated to |
endMonth | Date | — | Latest month that can be navigated to |
captionLayout | 'label' | 'dropdown' | 'dropdown-months' | 'dropdown-years' | 'buttons' | 'label' | Calendar header style; dropdown options require startMonth + endMonth |
showTime | boolean | false | Show hour / minute / AM-PM picker beside the calendar (single-date mode only) |
onClose | () => void | — | Called when the popover closes |
className | string | — | Additional className on the trigger |
| Prop | Type | Default | Description |
|---|---|---|---|
mode | 'single' | 'range' | 'multiple' | — | Selection mode |
selected | Date | DateRange | Date[] | — | Currently selected value(s) |
onSelect | (date) => void | — | Called when a date is selected |
disabled | (date: Date) => boolean | — | Return true to disable a date |
captionLayout | 'label' | 'dropdown' | 'dropdown-months' | 'dropdown-years' | 'buttons' | 'label' | Header style; dropdown options require startMonth + endMonth |
showOutsideDays | boolean | true | Show days from adjacent months |
numberOfMonths | number | 1 | Number of months to display side by side |
className | string | — | Additional className |
interface DatePreset {
label: string;
value: Date | [Date, Date] | (() => Date | [Date, Date]);
}The value can be a static date, a static range tuple, or a factory function (recommended for relative dates like "Last 30 Days" so they are always computed fresh at selection time).
Keyboard Navigation
The DatePicker calendar supports full keyboard navigation. Use arrow keys to navigate dates, Enter to select, and Escape to close the popover.