Controlled
One value and one callback.
const [date, setDate] = React.useState<Date | null>(null);
<DatePicker label="Start date" value={date} onValueChange={setDate}>
<DatePicker.Trigger />
<DatePicker.Content>
<DatePicker.Calendar />Selects a date or date range from a calendar.
<DatePicker label="Start date" placeholder="Pick a date">
<DatePicker.Trigger />
<DatePicker.Content>
<DatePicker.Calendar />
</DatePicker.Content>
</DatePicker>pnpm add @usefragments/uiImport styles once from @usefragments/ui/styles. See the installation guide for framework and theme setup.
react-day-picker — Optional — renders the calendar popup; the trigger and form field work without it.pnpm add react-day-pickerImport DatePicker once, then compose its parts with dot notation.
DatePicker.TriggerDatePicker.ContentDatePicker.CalendarDatePicker.PresetDatePicker.preloadTrigger opens one month; today carries the strong line.
<DatePicker label="Start date" placeholder="Pick a date">
<DatePicker.Trigger />
<DatePicker.Content>
<DatePicker.Calendar />
</DatePicker.Content>
</DatePicker>One value and one callback.
const [date, setDate] = React.useState<Date | null>(null);
<DatePicker label="Start date" value={date} onValueChange={setDate}>
<DatePicker.Trigger />
<DatePicker.Content>
<DatePicker.Calendar />Matches the height of other sm toolbar controls.
<DatePicker size="sm" label="Last scanned" placeholder="Any time">
<DatePicker.Trigger />
<DatePicker.Content>
<DatePicker.Calendar />
</DatePicker.Content>
</DatePicker>Two months side by side so both ends of the range are visible.
import type { DateRange } from "@usefragments/ui";
const [range, setRange] = React.useState<DateRange | null>(null);
<DatePicker
mode="range"Days before today are struck through and unclickable.
<DatePicker
label="Ships on"
placeholder="Select a future date"
disabledDates={(date) => date < new Date(new Date().setHours(0, 0, 0, 0))}
>
<DatePicker.Trigger />A few ghost buttons above the calendar skip the common ranges.
const today = new Date(2026, 9, 4);
const weekAgo = new Date(2026, 8, 28);
<DatePicker mode="range" label="Window" numberOfMonths={2}>
<DatePicker.Trigger />
<DatePicker.Content>Label above the trigger, supporting text below it.
<DatePicker
label="Renewal date"
placeholder="Pick a date"
helperText="Billing restarts on this day each month."
>
<DatePicker.Trigger />The danger edge on the trigger, and the reason below it with an icon.
<DatePicker
label="Start date"
placeholder="Pick a date"
invalid
errorMessage="Pick a start date before the end date"
>The value with a dashed edge; the calendar stays shut.
<DatePicker label="Start date" defaultValue={new Date(2026, 9, 3)} readOnly>
<DatePicker.Trigger />
<DatePicker.Content>
<DatePicker.Calendar />
</DatePicker.Content>
</DatePicker>Dimmed; the calendar will not open.
<DatePicker label="Start date" placeholder="Pick a date" disabled>
<DatePicker.Trigger />
<DatePicker.Content>
<DatePicker.Calendar />
</DatePicker.Content>
</DatePicker>Prop | Type | Default | Description |
|---|---|---|---|
Calendar.className | string | undefined | Not set | — |
Calendar.numberOfMonths | number | undefined | Not set | Override number of months from root |
childrenRequired | React.ReactNode | Not set | DatePicker trigger and content |
className | string | undefined | Not set | Wrapper class name |
Content.align | "center" | "start" | "end" | undefinedcenterstartend | Not set | — |
Content.childrenRequired | React.ReactNode | Not set | — |
Content.sideOffset | number | undefined | Not set | — |
defaultValue | Date | DateRange | null | undefined | Not set | Initial value for uncontrolled use, typed by mode |
disabled | boolean | undefined | false | Disable the picker |
disabledDates | Matcher | Matcher[] | undefined | Not set | Dates to disable (react-day-picker Matcher | Matcher[]) |
errorMessage | string | undefined | Not set | Words shown under the trigger while invalid, with a warning icon |
format | ((date: Date) => string) | undefined | Not set | Formats one date for the trigger. Defaults to Intl in the locale (long for single, medium for each range end); ranges join with an en dash |
helperText | string | undefined | Not set | Helper text shown below the date picker |
invalid | boolean | undefined | false | Marks the value invalid: the danger edge, aria-invalid and the error message |
label | string | undefined | Not set | Visible label text above the date picker |
locale | Locale | undefined | Not set | date-fns locale for i18n |
mode | "range" | "single" | undefinedrangesingle | single | Selection mode |
name | string | undefined | Not set | Hidden input name for forms |
numberOfMonths | number | undefined | 1 | Number of months displayed side-by-side |
onOpenChange | ((open: boolean) => void) | undefined | Not set | Popover open state change callback |
onValueChange | ((value: Date | null) => void) | ((value: DateRange | null) => void) | undefined | Not set | Called with the new value, typed by mode: (date: Date | null) in single mode, (range: DateRange | null) in range mode |
open | boolean | undefined | Not set | Controlled popover open state |
placeholder | string | undefined | Not set | Trigger placeholder text |
Preset.childrenRequired | React.ReactNode | Not set | — |
Preset.date | Date | undefined | Not set | Date to select (single mode) |
Preset.range | DateRange | undefined | Not set | Range to select (range mode) |
readOnly | boolean | undefined | false | Shows the value with a dashed edge; the calendar stays shut |
size | DatePickerSize | undefinedsmmd | "md" | Trigger size |
Trigger.children | React.ReactNode | Not set | — |
Trigger.placeholder | string | undefined | Not set | — |
value | Date | DateRange | null | undefined | Not set | Controlled value: Date | null in single mode, DateRange | null in range mode |