v1.0

Date Picker

Click-to-open trigger, styled like an Input, that reveals a Calendar panel in a popover.

Pro

Description

DatePicker is a compound component: DatePicker.Trigger renders a button styled with the same Input shell used across the form components, and DatePicker.Content anchors a Radix popover panel below it. The component has no opinion on what goes inside that panel. You fill it with Calendar, so whichever mode the calendar runs in, single, multiple, or range, is what the picker does.

Reach for it any time a date field should be picked from a grid instead of typed. It reads naturally as a filter trigger ("Last 30 days"), a booking field, or a due-date picker on a form, anywhere a typed date would force the user to remember a format.

If the user types the date directly, use Date Input instead, a masked DD/MM/YYYY field. If the calendar should always be visible rather than sitting behind a trigger, drop the trigger and render Calendar on its own.

DatePicker is a Create UI Pro component. With a Pro seat, npx @create-ui/cli add date-picker installs it, and the previews on this page are marked with a Pro badge. It pulls in Calendar and Button for the composed examples below.

Installation

pnpm dlx @create-ui/cli add date-picker

Anatomy

<DatePicker>
  <DatePicker.Trigger />
  <DatePicker.Content>
    <Calendar>
      <Calendar.Panel>
        <Calendar.Body>
          <Calendar.Months />
        </Calendar.Body>
      </Calendar.Panel>
    </Calendar>
  </DatePicker.Content>
</DatePicker>

DatePicker.Content is unstyled: no background, border, or padding of its own. All of the panel's visual chrome comes from Calendar.Panel, so it always needs to wrap whatever Calendar composition sits inside DatePicker.Content. A bare <Calendar /> with no panel floats with nothing behind it.

Usage

import { DatePicker } from "@/components/ui/date-picker"
<DatePicker>
  <DatePicker.Trigger />
  <DatePicker.Content>
    <Calendar />
  </DatePicker.Content>
</DatePicker>

Examples

Five examples: sizes and states cover the trigger's own props, then two compositions, single date and date range with presets, show how the closing behavior changes with Calendar's mode.

Sizes

size on the DatePicker root drives the trigger's Input shell height (xs, sm, md). It has no effect on the Calendar panel itself. Size that one separately if you want the grid to scale too.

Pro

xs

sm

md

States

invalid recolors the trigger's text and icon without touching the panel. disabled sets the native disabled attribute on the trigger button, so the popover can't open, and tints the shell to the disabled visual.

Pro

Invalid

Disabled

Single date

A single click is a complete choice, so there's nothing to confirm. Control open yourself and close it straight from Calendar's onChange, no footer, no Apply button.

Pro

Date range with presets

Calendar.Presets gives the range picker shortcuts like "Last 7 days" next to the grid. A range needs two clicks to complete, so Apply and Clear live in Calendar.Footer and only Apply closes the popover.

Pro

Accessibility

KeyDescription
Enter / SpaceOpens or closes the popover when the trigger is focused.
EscapeCloses the popover and returns focus to the trigger.
TabMoves focus into the panel when open: nav buttons, presets, day grid, footer actions, in that order.

ARIA notes:

  • DatePicker.Trigger renders a real <button> wrapped in Radix's Popover.Trigger, so it picks up aria-expanded and aria-haspopup automatically as the popover opens and closes.
  • The trigger sets no aria-invalid itself. invalid is a purely visual recolor, since the trigger is a button that opens a picker, not an editable field carrying its own validation state.
  • DatePicker.Content is Radix's Popover.Content, so focus trapping, dismiss-on-outside-click, and Escape-to-close all come from Radix's tested popover behavior.
  • Whatever you compose inside DatePicker.Content keeps its own accessibility contract. With Calendar, that means React Aria's grid semantics, roving tabindex, and selection announcements. See Calendar's accessibility notes for the day-grid details.

Styling

Tailwind override: pass className to DatePicker.Trigger to merge Tailwind classes with the component's classes (via cn()):

<DatePicker.Trigger className="w-64" placeholder="Select date" />

Data slots and attributes:

  • data-slot="date-picker" on the root.
  • data-slot="date-picker-trigger" on the Input-shell wrapper, data-slot="date-picker-trigger-button" on the underlying <button>.
  • data-slot="date-picker-content" on the popover panel, data-slot="date-picker-close" on DatePicker.Close.
  • data-state="open" / data-state="closed" on the trigger and content, mirrored from Radix Popover.
  • data-side on date-picker-content, reflecting which side of the trigger the panel actually rendered on.

Target the trigger's invalid state in CSS:

[data-slot="date-picker-trigger-button"][data-invalid] {
  /* … */
}
  • Calendar: the grid that fills DatePicker.Content. Use it alone when the calendar should stay visible instead of sitting behind a trigger.
  • Date Input: a masked, typed date field. Use it when the user knows the date and would rather type it than click through months.
  • Popover: the primitive DatePicker.Content is built on. Reach for it directly when the trigger isn't a date field.
  • Select: for picking from a short list of options rather than a date.

API Reference

DatePicker

The root. Renders Radix's Popover.Root and opens the context every other part reads size, invalid, disabled, and loading from. Extends React.ComponentProps<typeof Popover.Root>.

Props

PropTypeDefaultDescription
sizeInputSize"sm"Cascades to DatePicker.Trigger's Input shell height.
invalidbooleanfalseCascades to DatePicker.Trigger; recolors its text and icon.
disabledbooleanfalseCascades to DatePicker.Trigger; disables the trigger button.
loadingbooleanfalseCascades to DatePicker.Trigger's Input context.
openboolean-Radix. Controlled open state.
defaultOpenboolean-Radix. Uncontrolled initial open state.
onOpenChange(open: boolean) => void-Radix. Fires when the popover opens or closes.
childrenReact.ReactNode-A DatePicker.Trigger and a DatePicker.Content.

DatePicker.Trigger

The clickable, Input-styled button that opens the popover. Extends Omit<React.ComponentProps<typeof Popover.Trigger>, "asChild">.

Props

PropTypeDefaultDescription
sizeInputSizefrom DatePickerOverrides the size cascaded from the root.
invalidbooleanfrom DatePickerOverrides the invalid state cascaded from the root.
disabledbooleanfrom DatePickerOverrides the disabled state cascaded from the root.
leadingReact.ReactNode<RiCalendar2Line />Icon before the label. Pass leading={null} to remove it.
placeholderReact.ReactNode"Select date"Shown when children is empty.
classNamestring-Tailwind classes merged with the component's classes via cn().
childrenReact.ReactNode-The already-formatted display value. DatePicker does not format dates itself.

Variants

VariantOptionsDefaultDescription
size"xs" "sm" "md""sm"Height of the trigger's Input shell.

DatePicker.Content

The popover panel. Extends React.ComponentProps<typeof Popover.Content>.

Props

PropTypeDefaultDescription
align"start" | "center" | "end""start"Radix. Alignment against the trigger.
sideOffsetnumber8Radix. Gap between trigger and panel.
classNamestring-Tailwind classes merged with the component's classes via cn().
childrenReact.ReactNode-Usually a Calendar wrapped in Calendar.Panel.

DatePicker.Close

A thin re-export of Radix's Popover.Close, for closing the popover from inside DatePicker.Content (an Apply button, for example). Extends React.ComponentProps<typeof Popover.Close>.

Props

PropTypeDefaultDescription
asChildbooleanfalseRadix. Render as the child element via Slot.
childrenReact.ReactNode-Usually a Button.

Types

type DatePickerContextValue = {
  size: InputSize
  invalid: boolean
  disabled: boolean
  loading: boolean
}

InputSize comes from Input. useDatePickerContext() reads the current DatePickerContextValue, useful for a custom trigger replacement that still wants to follow the root's size / invalid / disabled state.