Date Picker
A button that opens a calendar in a popover, for one date or a range.
import {
DatePicker,
DatePickerContent,
DatePickerTrigger,
} from '@/components/ui/date-picker';
export default function DatePickerDemo() {
return (
<DatePicker>
<DatePickerTrigger />
<DatePickerContent />
</DatePicker>
);
}
Install
npx @madeui/cli add date-picker
Installs react-day-picker and date-fns alongside the component, plus the calendar, button, and popover components it composes.
Usage
import {
DatePicker,
DatePickerContent,
DatePickerTrigger,
} from '@/components/ui/date-picker';
<DatePicker>
<DatePickerTrigger />
<DatePickerContent />
</DatePicker>
Composition
<DatePicker>
<DatePickerTrigger>
<DatePickerValue />
</DatePickerTrigger>
<DatePickerContent>
{/* optional presets / controls, rendered above the calendar */}
</DatePickerContent>
</DatePicker>
Range
The popover stays open until both ends of the range are chosen. Two months sit
side by side on wide screens; below 640px the calendar falls back to a single
month so the popup still fits the viewport.
'use client';
import * as React from 'react';
import { addDays } from 'date-fns';
import {
DatePicker,
DatePickerContent,
DatePickerTrigger,
type DateRange,
} from '@/components/ui/date-picker';
export default function DatePickerRange() {
const [range, setRange] = React.useState<DateRange | undefined>({
from: new Date(),
to: addDays(new Date(), 6),
});
return (
<DatePicker mode="range" value={range} onValueChange={setRange}>
<DatePickerTrigger />
<DatePickerContent numberOfMonths={2} />
</DatePicker>
);
}
Dropdown caption
import {
DatePicker,
DatePickerContent,
DatePickerTrigger,
} from '@/components/ui/date-picker';
export default function DatePickerDropdown() {
return (
<DatePicker placeholder="Date of birth">
<DatePickerTrigger />
<DatePickerContent
captionLayout="dropdown"
defaultMonth={new Date(1990, 0)}
startMonth={new Date(1930, 0)}
endMonth={new Date()}
/>
</DatePicker>
);
}
Presets
Children of DatePickerContent render above the calendar; here a row of buttons
sets the controlled value. A preset is a complete choice, so the example also
controls open and closes the popover after the click.
'use client';
import * as React from 'react';
import * as stylex from '@stylexjs/stylex';
import { addDays } from 'date-fns';
import { Button } from '@/components/ui/button';
import {
DatePicker,
DatePickerContent,
DatePickerTrigger,
} from '@/components/ui/date-picker';
import { space } from '@/lib/constants.stylex';
const presets = [
{ label: 'Today', days: 0 },
{ label: 'Tomorrow', days: 1 },
{ label: 'In a week', days: 7 },
];
export default function DatePickerPresets() {
const [date, setDate] = React.useState<Date | undefined>();
const [open, setOpen] = React.useState(false);
return (
<DatePicker
open={open}
onOpenChange={setOpen}
value={date}
onValueChange={setDate}
>
<DatePickerTrigger />
<DatePickerContent>
<div {...stylex.props(styles.presets)}>
{presets.map(({ label, days }) => (
<Button
key={label}
variant="outline"
size="xs"
onClick={() => {
setDate(addDays(new Date(), days));
// A preset is a complete choice, so it closes the popover the
// same way picking a day in the calendar does.
setOpen(false);
}}
>
{label}
</Button>
))}
</div>
</DatePickerContent>
</DatePicker>
);
}
const styles = stylex.create({
presets: {
display: 'flex',
gap: space.s2,
},
});
Form
'use client';
import * as React from 'react';
import * as stylex from '@stylexjs/stylex';
import { format } from 'date-fns';
import { Button } from '@/components/ui/button';
import {
DatePicker,
DatePickerContent,
DatePickerTrigger,
} from '@/components/ui/date-picker';
import { Field, FieldDescription, FieldLabel } from '@/components/ui/field';
import { space, fontSize, container } from '@/lib/constants.stylex';
import { colors } from '@/lib/tokens.stylex';
export default function DatePickerForm() {
const [date, setDate] = React.useState<Date | undefined>();
const [submitted, setSubmitted] = React.useState<string | null>(null);
return (
<form
onSubmit={(event) => {
event.preventDefault();
setSubmitted(date ? format(date, 'yyyy-MM-dd') : 'none');
}}
{...stylex.props(styles.form)}
>
<Field>
<FieldLabel htmlFor="date-picker-form-trigger">Date of birth</FieldLabel>
<DatePicker value={date} onValueChange={setDate}>
<DatePickerTrigger id="date-picker-form-trigger" />
<DatePickerContent captionLayout="dropdown" endMonth={new Date()} />
</DatePicker>
<FieldDescription>Used to calculate your age.</FieldDescription>
</Field>
<div {...stylex.props(styles.row)}>
<Button type="submit">Submit</Button>
{submitted && (
<span {...stylex.props(styles.result)}>Submitted: {submitted}</span>
)}
</div>
</form>
);
}
const styles = stylex.create({
form: {
display: 'flex',
flexDirection: 'column',
gap: space.s4,
width: container.sm,
},
row: {
alignItems: 'center',
display: 'flex',
gap: space.s3,
},
result: {
color: colors.mutedForeground,
fontSize: fontSize.sm,
},
});
Disabled
import {
DatePicker,
DatePickerContent,
DatePickerTrigger,
} from '@/components/ui/date-picker';
export default function DatePickerDisabled() {
return (
<DatePicker disabled defaultValue={new Date()}>
<DatePickerTrigger />
<DatePickerContent />
</DatePicker>
);
}
API reference
Composes Popover, Button, and Calendar. The selection state lives in DatePicker; the tables below cover the props each part adds — everything else is forwarded to the part it wraps.
DatePicker (Root)
| Prop | Type | Default | Description |
|---|---|---|---|
mode |
'single' | 'range' |
'single' |
Selection mode. Decides the value type of the props below. |
value |
Date | undefined (single) / DateRange | undefined (range) |
— | Controlled value. |
defaultValue |
same as value |
— | Uncontrolled initial value. |
onValueChange |
(value) => void |
— | Called with the new value on every selection. |
format |
string |
'PPP' / 'PP' (range) |
date-fns format string used by DatePickerValue. A range prints two dates, so it defaults to the shorter form. |
locale |
Locale |
— | date-fns locale, applied to the formatted value and the calendar. |
placeholder |
string |
'Pick a date' / 'Pick a date range' |
Shown while nothing is selected. |
disabled |
boolean |
false |
Disables the trigger. |
open / defaultOpen / onOpenChange |
— | — | Forwarded to the Popover root. The popover closes itself after a single date is chosen, or once a range is complete. |
DatePickerTrigger
Renders this library’s Button as the popover trigger, with a leading calendar icon. Children default to <DatePickerValue />.
| Prop | Type | Default | Description |
|---|---|---|---|
variant |
ButtonVariant |
'outline' |
|
size |
ButtonSize |
'md' |
|
style |
StyleXStyles |
— | StyleX styles merged last — always win over the component’s own styles. |
DatePickerValue
A <span> with the formatted selection (range: from – to, or from alone while incomplete) or the placeholder. Muted while empty. Accepts style and all native span props.
DatePickerContent
PopoverContent with the wired Calendar (mode, selected, onSelect, locale, and defaultMonth come from the root). children render above the calendar in a column.
| Prop | Type | Default | Description |
|---|---|---|---|
side / sideOffset / align / alignOffset |
— | align="start" |
Popover positioning. |
size |
'sm' | 'md' |
'md' |
Calendar cell size. |
style |
StyleXStyles |
— | Merged last on the popup. |
numberOfMonths |
number |
1 |
Months shown side by side. Clamped to one below 640px, where a wider strip would not fit. |
autoFocus |
boolean |
true |
The calendar takes focus on open (the selected day, else today) so the arrow keys work straight away. |
resetOnSelect |
boolean |
true |
Range mode: a click on a complete range starts a new one instead of extending it. |
any other Calendar prop |
— | — | captionLayout, disabled, startMonth, endMonth, showWeekNumber, min, max, … are forwarded to Calendar. |
Popup width
The popup is as wide as the calendar asks for, clamped on both sides:
- Never narrower than the trigger. A single month is narrower than the default
trigger, so the popup stretches to
--anchor-widthand the calendar sits centred inside it. - Never wider than
--available-width, the room the viewport leaves next to the trigger. A two-month calendar is free to outgrow the trigger.
Set a width on the popup with style to override the rule, and on the trigger
to change what the popup matches.
Styling
DatePickerTrigger, DatePickerValue, and DatePickerContent accept style (StyleXStyles, merged last so caller overrides always win).