Thrive Design System

Components

DatePicker

A date and date range picker component with calendar popover, presets, and multiple trigger styles

Installation

npm install @thrivecart/ui
yarn add @thrivecart/ui
pnpm add @thrivecart/ui
bun add @thrivecart/ui

Usage

import { DatePicker } from '@thrivecart/ui';

Examples

Basic Date Picker

Single Date Selection

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>
);
}

Trigger Types

Button vs Input Trigger

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>

Range with Presets

Built-in presets:

  • Today
  • Yesterday
  • This Week
  • Last Week
  • This Month
  • Last Month
  • Custom (opens calendar for free selection)

Reporting Period

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>

Custom Presets

Custom Preset Options

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}
/>

Date Constraints

Future Dates Only

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>
);
}

Custom Date Format

Different Date Formats

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>

Month / Year Dropdown

Dropdown Caption Layout

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:

ValueDescription
labelStatic "Month Year" label (default)
dropdownBoth month and year dropdowns
dropdown-monthsMonth dropdown, static year label
dropdown-yearsYear dropdown, static month label
buttonsNavigation arrows only

Standalone Calendar with Dropdown

Pass captionLayout directly to the Calendar component

April 2026
<Calendar
mode="single"
selected={date}
onSelect={setDate}
captionLayout="dropdown"
startMonth={new Date(2020, 0, 1)}
endMonth={new Date(2030, 11, 31)}
/>

With Time Picker

Date & Time Selection

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>
);
}

Date Range with Time

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
/>

Dropdown Navigation + Time

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
/>

Disabled State

Disabled DatePicker

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>

In Form Context

Event Scheduling Form

DatePicker inside a Field component with label and helper text

Schedule Event

Select start and end dates

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>
);
}

Month Picker

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.

Basic Month Picker

Select a month from the 3×4 grid; navigate years with the arrows

2026
function BasicMonthPicker() {
const [value, setValue] = React.useState(new Date());

return (
  <MonthPicker value={value} onChange={setValue} />
);
}

With Year Constraints

Use minYear and maxYear to restrict navigation range

2026
<MonthPicker
value={value}
onChange={setValue}
minYear={2020}
maxYear={2030}
/>

MonthPicker Props

PropTypeDefaultDescription
valueDateCurrently selected date (month + year only)
onChange(date: Date) => voidCalled with a Date set to the 1st of the selected month
minYearnumberMinimum navigable year
maxYearnumberMaximum navigable year
classNamestringAdditional className

Calendar Component

The Calendar component can be used standalone for embedded date selection without a popover.

Single Month

Standalone Calendar

Embedded calendar for single date selection

April 2026
function StandaloneCalendar() {
const [date, setDate] = React.useState(new Date());

return (
  <Calendar
    mode="single"
    selected={date}
    onSelect={setDate}
  />
);
}

Range Calendar

Two-Month Range Calendar

Embedded calendar showing two months side-by-side for range selection

April 2026
May 2026
function RangeCalendar() {
const [range, setRange] = React.useState({});

return (
  <Calendar
    mode="range"
    selected={range}
    onSelect={(r) => setRange(r ?? {})}
    numberOfMonths={2}
  />
);
}

Best Practices

Provide presets for common selections

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".

Use appropriate constraints

Disable dates that are not valid selections — past dates for scheduling, future dates for historical reports, weekends for business-only selections.

Choose the right trigger type

Use input trigger (triggerType="input") in forms alongside other inputs for visual consistency. Use button trigger for standalone or toolbar date selections.

Show clear placeholders

Use descriptive placeholders that indicate what date is expected, e.g. "Select report period" or "Choose appointment date".

Don't allow impossible ranges

For date ranges, ensure end date cannot be before start date. The component handles this automatically when using isRange.

Don't hide the date format

Users should always see the selected date in a clear, readable format. Avoid overly technical format strings like ISO 8601 in the display value.

Props

DatePicker

PropTypeDefaultDescription
valueDate | [Date, Date]Selected date or date range
onChange(date: Date | [Date, Date] | undefined) => voidCalled when selection is applied
placeholderstring'Select date'Trigger placeholder text
isRangebooleanfalseEnable date range selection
hasPresetsbooleanfalseShow preset options sidebar
presetsDatePreset[]built-inCustom preset definitions; replaces defaults
triggerType'button' | 'input''button'Trigger visual style
disabledbooleanfalseDisable the picker entirely
dateFormatstring'MMM dd, yyyy'date-fns format string for display
disabledDays(date: Date) => booleanReturn true to disable a specific date
startMonthDateEarliest month that can be navigated to
endMonthDateLatest month that can be navigated to
captionLayout'label' | 'dropdown' | 'dropdown-months' | 'dropdown-years' | 'buttons''label'Calendar header style; dropdown options require startMonth + endMonth
showTimebooleanfalseShow hour / minute / AM-PM picker beside the calendar (single-date mode only)
onClose() => voidCalled when the popover closes
classNamestringAdditional className on the trigger

Calendar

PropTypeDefaultDescription
mode'single' | 'range' | 'multiple'Selection mode
selectedDate | DateRange | Date[]Currently selected value(s)
onSelect(date) => voidCalled when a date is selected
disabled(date: Date) => booleanReturn true to disable a date
captionLayout'label' | 'dropdown' | 'dropdown-months' | 'dropdown-years' | 'buttons''label'Header style; dropdown options require startMonth + endMonth
showOutsideDaysbooleantrueShow days from adjacent months
numberOfMonthsnumber1Number of months to display side by side
classNamestringAdditional className

DatePreset

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).

Accessibility

Keyboard Navigation

The DatePicker calendar supports full keyboard navigation. Use arrow keys to navigate dates, Enter to select, and Escape to close the popover.

  • Built on react-day-picker with full ARIA support
  • Arrow keys navigate between dates within the calendar
  • Enter selects the focused date
  • Escape closes the popover
  • Focus is trapped within the popover when open
  • Month/year navigation buttons are keyboard accessible
  • Cancel and Apply buttons are fully focusable