Add this component

npx zentauri-ui add date-picker

Accessibility notes

Keyboard accessible by default with semantic markup, visible focus treatment, and tokenized states. Add descriptive labels for icon-only or decorative usage.

Dependency notes

Check Installation for shared peers. This component keeps styling in Tailwind classes and the --zui-* token contract.

Forms

Date picker with popover calendar

Use the date picker for form-friendly date selection: a trigger button opens a popover calendar, the chosen date renders as Intl-formatted text, and hidden inputs post local yyyy-MM-dd values with plain HTML forms.

Date picker variants playground

Pick a mode, appearance, size, and animation to preview the date picker live — open the popover, select a date or range, and try the clear affordance. Toggle Show output / Show code and the snippet updates to match the selected variant.


Date Picker API

Generated from the package prop types and variant definitions.

DatePicker

DatePickerProps

Variants

PropTypeDefault
appearance
aquabluecrimsoncyandefaultelectricemeraldforestghostglassgradient-bluegradient-green+31
default
size
lgmdsm
md

State

PropTypeDefault
defaultOpenboolean | undefinednone
defaultValueDate | DateRange | undefinednone
onOpenChange((open: boolean) => void) | undefinednone
onValueChange((value: Date | undefined) => void) | ((value: DateRange | undefined) => void) | undefinednone
openboolean | undefinednone
valueDate | DateRange | undefinednone

Behavior

PropTypeDefault
calendarComponentComponentType<CalendarBaseProps> | undefinednone
captionLayoutMonth/year title vs select dropdowns. Default `"label"`.'label' | 'dropdowns' | undefinednone
clearableShow a clear affordance in the trigger. Default false.boolean | undefinednone
closeOnSelectClose the popover on selection. Default true: single closes on pick, range closes once `to` is set.boolean | undefinednone
disabledWhole-control disabled (the calendar day matcher is `disabledDates`).boolean | undefinednone
disabledDatesDay matcher threaded to the calendar's `disabled` prop.DateMatcher | DateMatcher[] | undefinednone
firstDayOfWeek0 = Sunday … 6 = Saturday. Default: locale's `Intl` week info.0 | 1 | 4 | 6 | 2 | 3 | 5 | undefinednone
fixedWeeksAlways 6 rows for stable height across months. Default false.boolean | undefinednone
formatOptionsIntl options for the trigger text. Default `{ dateStyle: "medium" }`.Intl.DateTimeFormatOptions | undefinednone
fromYearDropdown year range lower bound. Default: today's year - 100.number | undefinednone
localeBCP 47 locale tag. Default: runtime locale.string | undefinednone
maxDateNavigation + selection upper bound.Date | undefinednone
minDateNavigation + selection lower bound.Date | undefinednone
modeSelection mode (no "multiple" — the calendar covers it inline).'single' | 'range' | undefinednone
nameRenders hidden input(s) with local `yyyy-MM-dd` value(s) for form posts.string | undefinednone
numberOfMonthsMonths rendered side by side. Default 1, capped at 3.number | undefinednone
placeholderTrigger text when nothing is selected.string | undefinednone
showOutsideDaysShow trailing/leading days of adjacent months. Default true.boolean | undefinednone
todayToday's date override — required for deterministic tests/SSR snapshots. Default: `new Date()` computed once on mount.Date | undefinednone
toYearDropdown year range upper bound. Default: today's year + 100.number | undefinednone
weekdayFormatWeekday header label format. Default `"short"`.'long' | 'short' | 'narrow' | undefinednone
Inherited HTML props
PropTypeDefault
classNamestring | undefinednone
idstring | undefinednone
onClickMouseEventHandler<HTMLButtonElement> | undefinednone
styleCSSProperties | undefinednone
titlestring | undefinednone

DatePickerCalendar

DatePickerCalendarProps

Behavior

PropTypeDefault
captionLayoutMonth/year title vs select dropdowns. Default `"label"`.'label' | 'dropdowns' | undefinednone
firstDayOfWeek0 = Sunday … 6 = Saturday. Default: locale's `Intl` week info.0 | 1 | 4 | 6 | 2 | 3 | 5 | undefinednone
fixedWeeksAlways 6 rows for stable height across months. Default false.boolean | undefinednone
fromYearDropdown year range lower bound. Default: today's year - 100.number | undefinednone
localeBCP 47 locale tag. Default: runtime locale.string | undefinednone
maxDateNavigation + selection upper bound.Date | undefinednone
minDateNavigation + selection lower bound.Date | undefinednone
numberOfMonthsMonths rendered side by side. Default 1, capped at 3.number | undefinednone
showOutsideDaysShow trailing/leading days of adjacent months. Default true.boolean | undefinednone
todayToday's date override — required for deterministic tests/SSR snapshots. Default: `new Date()` computed once on mount.Date | undefinednone
toYearDropdown year range upper bound. Default: today's year + 100.number | undefinednone
weekdayFormatWeekday header label format. Default `"short"`.'long' | 'short' | 'narrow' | undefinednone

DatePickerCommon

DatePickerCommonProps

State

PropTypeDefault
defaultOpenboolean | undefinednone
onOpenChange((open: boolean) => void) | undefinednone
openboolean | undefinednone

Behavior

PropTypeDefault
appearance'default' | 'outline' | 'ghost' | 'blue' | 'cyan' | 'green' | 'lime' | 'mint' | 'ocean' | 'sapphire' | 'lavender' | 'ruby' | 'red' | 'slate' | 'zinc' | 'stone' | 'royal' | 'electric' | 'forest' | 'sunset' | 'magenta' | 'crimson' | 'aqua' | 'plum' | 'sky' | 'rose' | 'purple' | 'pink' | 'orange' | 'yellow' | 'teal' | 'indigo' | 'emerald' | 'gradient-blue' | 'gradient-green' | 'gradient-red' | 'gradient-yellow' | 'gradient-purple' | 'gradient-teal' | 'gradient-indigo' | 'gradient-pink' | 'gradient-orange' | 'glass' | null | undefinednone
calendarComponentComponentType<CalendarBaseProps> | undefinednone
captionLayoutMonth/year title vs select dropdowns. Default `"label"`.'label' | 'dropdowns' | undefinednone
clearableShow a clear affordance in the trigger. Default false.boolean | undefinednone
closeOnSelectClose the popover on selection. Default true: single closes on pick, range closes once `to` is set.boolean | undefinednone
disabledWhole-control disabled (the calendar day matcher is `disabledDates`).boolean | undefinednone
disabledDatesDay matcher threaded to the calendar's `disabled` prop.DateMatcher | DateMatcher[] | undefinednone
firstDayOfWeek0 = Sunday … 6 = Saturday. Default: locale's `Intl` week info.0 | 1 | 4 | 6 | 2 | 3 | 5 | undefinednone
fixedWeeksAlways 6 rows for stable height across months. Default false.boolean | undefinednone
formatOptionsIntl options for the trigger text. Default `{ dateStyle: "medium" }`.Intl.DateTimeFormatOptions | undefinednone
fromYearDropdown year range lower bound. Default: today's year - 100.number | undefinednone
localeBCP 47 locale tag. Default: runtime locale.string | undefinednone
maxDateNavigation + selection upper bound.Date | undefinednone
minDateNavigation + selection lower bound.Date | undefinednone
nameRenders hidden input(s) with local `yyyy-MM-dd` value(s) for form posts.string | undefinednone
numberOfMonthsMonths rendered side by side. Default 1, capped at 3.number | undefinednone
placeholderTrigger text when nothing is selected.string | undefinednone
showOutsideDaysShow trailing/leading days of adjacent months. Default true.boolean | undefinednone
size'md' | 'sm' | 'lg' | null | undefinednone
todayToday's date override — required for deterministic tests/SSR snapshots. Default: `new Date()` computed once on mount.Date | undefinednone
toYearDropdown year range upper bound. Default: today's year + 100.number | undefinednone
weekdayFormatWeekday header label format. Default `"short"`.'long' | 'short' | 'narrow' | undefinednone
Inherited HTML props
PropTypeDefault
classNamestring | undefinednone
idstring | undefinednone
onClickMouseEventHandler<HTMLButtonElement> | undefinednone
styleCSSProperties | undefinednone
titlestring | undefinednone

DatePickerSelection

DatePickerSelectionProps

State

PropTypeDefault
defaultValueDate | DateRange | undefinednone
onValueChange((value: Date | undefined) => void) | ((value: DateRange | undefined) => void) | undefinednone
valueDate | DateRange | undefinednone

Behavior

PropTypeDefault
modeSelection mode (no "multiple" — the calendar covers it inline).'single' | 'range' | undefinednone

DatePickerVariant

DatePickerVariantProps

Behavior

PropTypeDefault
appearance'default' | 'outline' | 'ghost' | 'blue' | 'cyan' | 'green' | 'lime' | 'mint' | 'ocean' | 'sapphire' | 'lavender' | 'ruby' | 'red' | 'slate' | 'zinc' | 'stone' | 'royal' | 'electric' | 'forest' | 'sunset' | 'magenta' | 'crimson' | 'aqua' | 'plum' | 'sky' | 'rose' | 'purple' | 'pink' | 'orange' | 'yellow' | 'teal' | 'indigo' | 'emerald' | 'gradient-blue' | 'gradient-green' | 'gradient-red' | 'gradient-yellow' | 'gradient-purple' | 'gradient-teal' | 'gradient-indigo' | 'gradient-pink' | 'gradient-orange' | 'glass' | null | undefinednone
size'md' | 'sm' | 'lg' | null | undefinednone

DatePickerAnimated

DatePickerAnimatedProps

animated

State

PropTypeDefault
defaultOpenboolean | undefinednone
defaultValueDate | DateRange | undefinednone
onOpenChange((open: boolean) => void) | undefinednone
onValueChange((value: Date | undefined) => void) | ((value: DateRange | undefined) => void) | undefinednone
openboolean | undefinednone
valueDate | DateRange | undefinednone

Behavior

PropTypeDefault
animationMonth-navigation animation for the embedded calendar.DatePickerAnimation | undefinednone
appearance'default' | 'outline' | 'ghost' | 'blue' | 'cyan' | 'green' | 'lime' | 'mint' | 'ocean' | 'sapphire' | 'lavender' | 'ruby' | 'red' | 'slate' | 'zinc' | 'stone' | 'royal' | 'electric' | 'forest' | 'sunset' | 'magenta' | 'crimson' | 'aqua' | 'plum' | 'sky' | 'rose' | 'purple' | 'pink' | 'orange' | 'yellow' | 'teal' | 'indigo' | 'emerald' | 'gradient-blue' | 'gradient-green' | 'gradient-red' | 'gradient-yellow' | 'gradient-purple' | 'gradient-teal' | 'gradient-indigo' | 'gradient-pink' | 'gradient-orange' | 'glass' | null | undefinednone
calendarComponentComponentType<CalendarBaseProps> | undefinednone
captionLayoutMonth/year title vs select dropdowns. Default `"label"`.'label' | 'dropdowns' | undefinednone
clearableShow a clear affordance in the trigger. Default false.boolean | undefinednone
closeOnSelectClose the popover on selection. Default true: single closes on pick, range closes once `to` is set.boolean | undefinednone
disabledWhole-control disabled (the calendar day matcher is `disabledDates`).boolean | undefinednone
disabledDatesDay matcher threaded to the calendar's `disabled` prop.DateMatcher | DateMatcher[] | undefinednone
firstDayOfWeek0 = Sunday … 6 = Saturday. Default: locale's `Intl` week info.0 | 1 | 4 | 6 | 2 | 3 | 5 | undefinednone
fixedWeeksAlways 6 rows for stable height across months. Default false.boolean | undefinednone
formatOptionsIntl options for the trigger text. Default `{ dateStyle: "medium" }`.Intl.DateTimeFormatOptions | undefinednone
fromYearDropdown year range lower bound. Default: today's year - 100.number | undefinednone
localeBCP 47 locale tag. Default: runtime locale.string | undefinednone
maxDateNavigation + selection upper bound.Date | undefinednone
minDateNavigation + selection lower bound.Date | undefinednone
modeSelection mode (no "multiple" — the calendar covers it inline).'single' | 'range' | undefinednone
nameRenders hidden input(s) with local `yyyy-MM-dd` value(s) for form posts.string | undefinednone
numberOfMonthsMonths rendered side by side. Default 1, capped at 3.number | undefinednone
placeholderTrigger text when nothing is selected.string | undefinednone
refAllows getting a ref to the component instance. Once the component unmounts, React will set `ref.current` to `null` (or call the ref with `null` if you passed a callback ref).Ref<HTMLButtonElement> | undefinednone
showOutsideDaysShow trailing/leading days of adjacent months. Default true.boolean | undefinednone
size'md' | 'sm' | 'lg' | null | undefinednone
todayToday's date override — required for deterministic tests/SSR snapshots. Default: `new Date()` computed once on mount.Date | undefinednone
toYearDropdown year range upper bound. Default: today's year + 100.number | undefinednone
weekdayFormatWeekday header label format. Default `"short"`.'long' | 'short' | 'narrow' | undefinednone
Inherited HTML props
PropTypeDefault
classNamestring | undefinednone
idstring | undefinednone
onClickMouseEventHandler<HTMLButtonElement> | undefinednone
styleCSSProperties | undefinednone
titlestring | undefinednone
CSS variable overrides

Date picker CSS variables

Override these date picker trigger variables on :root, a theme selector, or a component wrapper. Named-color appearances follow the same *-border / *-fg / *-bg-hover pattern shown for blue; the embedded calendar uses the --zui-calendar-* variables.

30 variables

Pattern: --zui-<component>-<slot?>-<variant?>-<property>-<state?>-dark?

:root {
  --zui-date-picker-trigger-radius: 0.5rem;
  --zui-date-picker-trigger-h-sm: 2rem;
  --zui-date-picker-trigger-h-md: 2.5rem;
  --zui-date-picker-trigger-h-lg: 3rem;
  --zui-date-picker-trigger-px: 0.75rem;
  --zui-date-picker-trigger-bg: #ffffff;
  --zui-date-picker-trigger-fg: oklch(13% 0.028 261.692);
  --zui-date-picker-trigger-default-border: oklch(87.2% 0.01 258.338);
  --zui-date-picker-placeholder-fg: oklch(44.6% 0.043 257.281);
  --zui-date-picker-ring: oklch(44.6% 0.03 256.802);
  --zui-date-picker-clear-fg: oklch(44.6% 0.043 257.281);
  --zui-date-picker-clear-bg-hover: #0000000d;
  --zui-date-picker-trigger-blue-border: #2563eb;
  --zui-date-picker-trigger-blue-fg: #2563eb;
  --zui-date-picker-trigger-blue-bg-hover: color-mix(in oklch, #2563eb 8%, transparent);
}

/* Dark theme variables follow the same names with -dark appended. */
.dark {
  --zui-date-picker-trigger-bg-dark: oklch(12.9% 0.042 264.695);
  --zui-date-picker-trigger-fg-dark: #ffffff;
  /* ...same variables with -dark at the end */
}

What it does

The date picker composes the popover and calendar components into an input-like control. Single mode closes on pick; range mode stays open until the end date is set. The selected value renders as locale-formatted text on the trigger, with an optional clear affordance.

The calendar day matcher is exposed as disabledDates, while the picker's own disabled prop is the standard whole-control boolean.

Composition and API

Value and open state are both controllable. Calendar options — locale, firstDayOfWeek, min/max bounds, numberOfMonths, caption dropdowns — thread straight through, and the picker's appearance colors both the trigger and the embedded calendar so they always match. The animated entry adds framer-motion month transitions inside the popover.

Common use cases

  • Booking and reservation forms with check-in/check-out ranges.
  • Deadline and due-date fields in dashboards and task managers.
  • Birthday fields using the dropdown caption for fast year jumps.
  • Report filters where a formatted range reads back at a glance.

Accessibility

The trigger sets aria-haspopup="dialog" and aria-expanded; the popover is a labeled dialog containing the ARIA-grid calendar. Focus moves into the grid on open and returns to the trigger on close.

This preview page exposes a single h1 in the hero for a clean outline.

Next.js integration notes

Keep the picker in a client component and pass explicit today and locale props in SSR-critical paths for deterministic markup.

Set NEXT_PUBLIC_SITE_URL so canonical and Open Graph URLs resolve on deploy.

FAQ

Does the Date Picker component work with plain HTML forms?

Yes. Pass a name prop and it renders hidden inputs with local-date yyyy-MM-dd values (name and name-end for ranges), built with local getters so values never shift across timezones.

How is the trigger text formatted?

Through Intl.DateTimeFormat with your formatOptions (default { dateStyle: "medium" }). Complete ranges use formatRange when the runtime supports it, with a join fallback.

Is the Date Picker keyboard accessible?

Yes. The trigger opens with click or ArrowDown, focus moves into the calendar grid, Escape closes and returns focus to the trigger, and the embedded calendar implements the full ARIA grid keyboard pattern.