Skip to content
Esc
navigateopen⌘Jpreview
On this page

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

Used to calculate your age.

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

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

Was this page helpful?