DatePicker

View source
An internationalized date picker component.

Introduction

MDatePicker is an internationalized date picker built on @internationalized/date for timezone and locale handling. It supports single date, date range, and multiple date selection, with button trigger, clearable, and quick preset interactions.

Built on Nuxt UI's Calendar component
Uses the @internationalized/date library for date handling, ensuring timezone safety and i18n support.

Usage

The default button trigger displays the current date. Click to open the calendar panel and write the selected date to v-model:

<script setup lang="ts">
const value = ref("")
</script>

<template>
  <MDatePicker v-model="value" />
</template>

range Date Range

range mode maintains start / end dates. numberOfMonths can display a dual-month calendar simultaneously:

<script setup lang="ts">
const value = ref("")
</script>

<template>
  <MDatePicker v-model="value" range :number-of-months="2" />
</template>

numberOfMonths Number of Months

numberOfMonths controls the number of months displayed side by side in the popover, making cross-month selection easier:

<template>
  <MDatePicker :number-of-months="3" />
</template>

clearable Clearable

clearable shows a clear entry when a value is set. Clicking it resets without opening the calendar:

<template>
  <MDatePicker clearable />
</template>

presets Quick Presets

Setting presets to default automatically generates quick items like "Today", "This Week", "This Month" based on the current mode:

<template>
  <MDatePicker range presets="default" />
</template>

multiple Multiple Date Selection

multiple mode saves multiple dates in an array. The button label can dynamically display the number of selected dates:

<template>
  <MDatePicker multiple :button-props="{ label: 'Select multiple dates', color: 'primary' }" />
</template>

buttonProps Trigger Button

buttonProps is passed to the trigger button, allowing adjustment of label, color, variant, and icon:

<template>
  <MDatePicker
    :button-props="{
      label: 'Select birthday',
      color: 'primary',
      variant: 'outline',
      icon: 'i-lucide-cake'
    }"
  />
</template>

Examples

Inheriting Field Context

When placed inside UFormField, it inherits size and error state, and the trigger button renders according to the form state:

Example error state
<template>
  <UFormField label="Appointment Date" size="xs" error="Example error state">
    <MDatePicker />
  </UFormField>
</template>

Inside UFieldGroup

When placed alongside a button inside UFieldGroup, they share size and border connection — ideal for filter bars with shortcut actions:

<template>
  <UFieldGroup size="xs">
    <MDatePicker />
    <UButton icon="i-lucide-calendar-check" color="neutral" variant="subtle" />
  </UFieldGroup>
</template>

Constraining Selectable Date Boundaries

minValue and maxValue disable dates outside the boundary, restricting selection to future or past dates respectively:

<script setup lang="ts">
import type { CalendarDate } from '#movk/types'

const formatter = useDateFormatter()
const futureDate = shallowRef<CalendarDate>()
const pastDate = shallowRef<CalendarDate>()
</script>

<template>
  <div class="space-y-4">
    <UFormField label="未来日期">
      <MDatePicker
        v-model="futureDate"
        :min-value="formatter.getToday()"
        :button-props="{ label: '选择未来日期', class: 'w-full' }"
      />
    </UFormField>

    <UFormField label="过去日期">
      <MDatePicker
        v-model="pastDate"
        :max-value="formatter.getToday()"
        :button-props="{ label: '选择过去日期', class: 'w-full' }"
      />
    </UFormField>
  </div>
</template>

Disabling Dates by Rule

isDateUnavailable disables dates according to business rules (e.g., weekends as shown in the example):

<script setup lang="ts">
import { CalendarDate } from '#movk/composables/useDateFormatter'
import type { DateValue } from '#movk/types'

const formatter = useDateFormatter()
const date = shallowRef(new CalendarDate(2025, 11, 18))

// 禁用周末
const isDateUnavailable = (date: DateValue) => {
  return formatter.isWeekend(date)
}
</script>

<template>
  <MDatePicker
    v-model="date"
    :is-date-unavailable="isDateUnavailable"
    :button-props="{ label: '仅工作日', class: 'w-full' }"
  />
</template>

Custom Trigger Button Label

labelFormat receives formatting utilities and the current value to combine date and weekday info into the button label:

<script setup lang="ts">
import { CalendarDate } from '@internationalized/date'

const isoDate = shallowRef(new CalendarDate(2025, 11, 18))
const timestampDate = shallowRef(new CalendarDate(2025, 11, 18))
const customDate = shallowRef(new CalendarDate(2025, 11, 18))
</script>

<template>
  <div class="space-y-4">
    <UFormField label="ISO 格式">
      <MDatePicker v-model="isoDate" label-format="iso" :button-props="{ class: 'w-full' }" />
    </UFormField>

    <UFormField label="时间戳">
      <MDatePicker v-model="timestampDate" label-format="timestamp" :button-props="{ class: 'w-full' }" />
    </UFormField>

    <UFormField label="自定义">
      <MDatePicker
        v-model="customDate"
        :button-props="{ class: 'w-full' }"
        :label-format="(fmt, value) => {
          if (!value || !fmt.isDateValue(value)) return '选择日期'
          return `${fmt.format(value)} (星期${fmt.getDayOfWeek(value)})`
        }"
      />
    </UFormField>
  </div>
</template>

Custom Quick Presets

Passing an array to presets allows business-rule-based date returns. Each item's value is a function that receives a formatter:

<script setup lang="ts">
import type { DateValue } from '@movk/nuxt'

const value = shallowRef<DateValue>()
</script>

<template>
  <MDatePicker
    v-model="value"
    placeholder="选择日期"
    :presets="[
      { label: '本周一', value: f => f.getStartOfWeek(f.getToday()) as DateValue },
      { label: '本月初', value: f => f.getStartOfMonth(f.getToday()) as DateValue },
      { label: '本年初', value: f => f.getStartOfYear(f.getToday()) as DateValue }
    ]"
  />
</template>

API

Props

Prop Default Type
as'div'any

The element or component this component should render as.

idstring
namestring
placeholder'选择日期'string
buttonPropsButtonProps
popoverPropsPopoverProps<P>
labelFormat'formatted'"iso" | "timestamp" | "unix" | "date" | "formatted" | (formatter: { format: (date: DateValue) => string; formatRange: (start: DateValue, end: DateValue, separator?: string) => string; formatArray: (dates: DateValue[], separator?: string) => string; ... 37 more ...; timeZone: string; }, modelValue: CalendarModelValue<...>): string
valueFormat'date-value'V

v-model 投影输出格式

presets"default" | DatePickerPreset<R, M>[]

快捷预设

locale'zh-CN'string

语言区域

formatOptionsIntl.DateTimeFormatOptions

日期格式化选项

timeZonestring

时区标识符,默认使用本地时区

type'date'"date" | "month" | "year"

The type of picker.

  • date renders a day calendar whose heading can switch to a month then year view.
  • month renders a standalone month picker.
  • year renders a standalone year picker.
nextYearIconappConfig.ui.icons.chevronDoubleRightany

The icon to use for the next year control.

nextYearOmit<ButtonProps, LinkPropsKeys>

Configure the next year button. { color: 'neutral', variant: 'ghost' }

nextMonthIconappConfig.ui.icons.chevronRightany

The icon to use for the next month control.

nextMonthOmit<ButtonProps, LinkPropsKeys>

Configure the next month button. { color: 'neutral', variant: 'ghost' }

prevYearIconappConfig.ui.icons.chevronDoubleLeftany

The icon to use for the previous year control.

prevYearOmit<ButtonProps, LinkPropsKeys>

Configure the prev year button. { color: 'neutral', variant: 'ghost' }

prevMonthIconappConfig.ui.icons.chevronLeftany

The icon to use for the previous month control.

prevMonthOmit<ButtonProps, LinkPropsKeys>

Configure the prev month button. { color: 'neutral', variant: 'ghost' }

viewControltrueboolean | Omit<ButtonProps, LinkPropsKeys>

Whether to make the heading a button that switches between the day, month and year views. Has no effect when type is year. Can be an object to override the button props. { color: 'neutral', variant: 'ghost', block: true }

color'primary'"primary" | "secondary" | "info" | "success" | "warning" | "error" | "important" | "neutral"
variant'solid'"solid" | "outline" | "soft" | "subtle"
size'md'"xs" | "sm" | "md" | "lg" | "xl"
rangeR

Whether or not a range of dates can be selected

multipleM

Whether or not multiple dates can be selected

defaultValueCalendarDefaultValue<R, M>
defaultPlaceholderCalendarDate | CalendarDateTime | ZonedDateTime

The default placeholder date

maximumDaysnumber

The maximum number of days that can be selected in a range

weekStartsOn0 | 1 | 2 | 4 | 5 | 3 | 6

The day of the week to start the calendar on

weekdayFormat"narrow" | "short" | "long"

The format to use for the weekday strings provided via the weekdays slot prop

maxValueCalendarDate | CalendarDateTime | ZonedDateTime

The maximum date that can be selected

minValueCalendarDate | CalendarDateTime | ZonedDateTime

The minimum date that can be selected

numberOfMonthsnumber

The number of months to display at once

isDateDisabledMatcher

A function that returns whether or not a date is disabled

isDateUnavailableMatcher

A function that returns whether or not a date is unavailable

isDateHighlightableMatcher

A function that returns whether or not a date is hightable

nextPage(placeholder: DateValue) => DateValue

A function that returns the next page of the calendar. It receives the current placeholder as an argument inside the component.

prevPage(placeholder: DateValue) => DateValue

A function that returns the previous page of the calendar. It receives the current placeholder as an argument inside the component.

fixedDate"start" | "end"

Which part of the range should be fixed

isMonthDisabledMatcher

A function that returns whether or not a month is disabled

isMonthUnavailableMatcher

A function that returns whether or not a month is unavailable

isYearDisabledMatcher

A function that returns whether or not a year is disabled

isYearUnavailableMatcher

A function that returns whether or not a year is unavailable

modelValueFormattedValue<R, M, V>
clearableboolean
monthControlsboolean

Show month controls

yearControlsboolean

Show year controls

weekNumbersboolean
allowNonContiguousRangesboolean

When combined with isDateUnavailable, determines whether non-contiguous ranges, i.e. ranges containing unavailable dates, may be selected.

pagedNavigationboolean

This property causes the previous and next buttons to navigate by the number of months displayed at once, rather than one month

preventDeselectboolean

Whether or not to prevent the user from deselecting a date without selecting another date first

fixedWeeksboolean

Whether or not to always display 6 weeks in the calendar

disabledboolean

Whether or not the calendar is disabled

readonlyboolean

Whether or not the calendar is readonly

initialFocusboolean

If true, the calendar will focus the selected day, today, or the first day of the month depending on what is visible when the calendar is mounted

disableDaysOutsideCurrentViewboolean

Whether or not to disable days outside the current view.

uiRecord<string, ClassNameValue> & { content?: SlotClass; arrow?: SlotClass; wrapper?: SlotClass; presets?: SlotClass; presetButton?: SlotClass; calendar?: SlotClass; clearIcon?: SlotClass; }

Emits

Event Type
update:modelValue[value: FormattedValue<R, M, V>]
update:placeholder[date: DateValue]
update:validModelValue[date: DateRange]
update:startValue[date: DateValue]
close:prevent[]
update:open[value: boolean]

Slots

Slot Type
default{ open: boolean; }
contentSlotProps<P>
heading{ value: string; view: CalendarView; date: DateValue; setView: (view: CalendarView) => void; setPlaceholder: (date: DateValue) => void; }
dayPick<CalendarCellTriggerProps, "day">
week-day{ day: string; }
month-cell{ month: DateValue; selected: boolean; disabled: boolean; }
year-cell{ year: DateValue; selected: boolean; disabled: boolean; }
leading{ ui: { base: (props?: Record<string, any>) => string; label: (props?: Record<string, any>) => string; leadingIcon: (props?: Record<string, any>) => string; leadingAvatar: (props?: Record<string, any>) => string; leadingAvatarSize: (props?: Record<string, any>) => string; trailingIcon: (props?: Record<string, any>) => string; }; }
trailing{ ui: { base: (props?: Record<string, any>) => string; label: (props?: Record<string, any>) => string; leadingIcon: (props?: Record<string, any>) => string; leadingAvatar: (props?: Record<string, any>) => string; leadingAvatarSize: (props?: Record<string, any>) => string; trailingIcon: (props?: Record<string, any>) => string; }; }

Theme

app.config.ts
export default defineAppConfig({
  ui: {
    datePicker: {
      slots: {
        wrapper: 'flex',
        presets: 'flex flex-col gap-1 p-2 border-r border-default min-w-[120px]',
        presetButton: 'justify-start',
        calendar: 'p-2',
        clearIcon: 'cursor-pointer opacity-60 hover:opacity-100 transition-opacity'
      },
      variants: {
        withPresets: {
          true: {},
          false: {
            wrapper: 'block'
          }
        }
      },
      defaultVariants: {
        withPresets: false
      }
    }
  }
})

Changelog

No recent changes
Copyright © 2025 - 2026 YiXuan - MIT License