DatePicker
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.
@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:
<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' | anyThe element or component this component should render as. |
id | string | |
name | string | |
placeholder | '选择日期' | string |
buttonProps | ButtonProps | |
popoverProps | PopoverProps<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' | Vv-model 投影输出格式 |
presets | "default" | DatePickerPreset<R, M>[]快捷预设 | |
locale | 'zh-CN' | string语言区域 |
formatOptions | Intl.DateTimeFormatOptions日期格式化选项 | |
timeZone | string时区标识符,默认使用本地时区 | |
type | 'date' | "date" | "month" | "year"The type of picker.
|
nextYearIcon | appConfig.ui.icons.chevronDoubleRight | anyThe icon to use for the next year control. |
nextYear | Omit<ButtonProps, LinkPropsKeys>Configure the next year button.
| |
nextMonthIcon | appConfig.ui.icons.chevronRight | anyThe icon to use for the next month control. |
nextMonth | Omit<ButtonProps, LinkPropsKeys>Configure the next month button.
| |
prevYearIcon | appConfig.ui.icons.chevronDoubleLeft | anyThe icon to use for the previous year control. |
prevYear | Omit<ButtonProps, LinkPropsKeys>Configure the prev year button.
| |
prevMonthIcon | appConfig.ui.icons.chevronLeft | anyThe icon to use for the previous month control. |
prevMonth | Omit<ButtonProps, LinkPropsKeys>Configure the prev month button.
| |
viewControl | true | boolean | Omit<ButtonProps, LinkPropsKeys>Whether to make the heading a button that switches between the day, month and year views.
Has no effect when |
color | 'primary' | "primary" | "secondary" | "info" | "success" | "warning" | "error" | "important" | "neutral" |
variant | 'solid' | "solid" | "outline" | "soft" | "subtle" |
size | 'md' | "xs" | "sm" | "md" | "lg" | "xl" |
range | RWhether or not a range of dates can be selected | |
multiple | MWhether or not multiple dates can be selected | |
defaultValue | CalendarDefaultValue<R, M> | |
defaultPlaceholder | CalendarDate | CalendarDateTime | ZonedDateTimeThe default placeholder date | |
maximumDays | numberThe maximum number of days that can be selected in a range | |
weekStartsOn | 0 | 1 | 2 | 4 | 5 | 3 | 6The 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 | |
maxValue | CalendarDate | CalendarDateTime | ZonedDateTimeThe maximum date that can be selected | |
minValue | CalendarDate | CalendarDateTime | ZonedDateTimeThe minimum date that can be selected | |
numberOfMonths | numberThe number of months to display at once | |
isDateDisabled | MatcherA function that returns whether or not a date is disabled | |
isDateUnavailable | MatcherA function that returns whether or not a date is unavailable | |
isDateHighlightable | MatcherA function that returns whether or not a date is hightable | |
nextPage | (placeholder: DateValue) => DateValueA function that returns the next page of the calendar. It receives the current placeholder as an argument inside the component. | |
prevPage | (placeholder: DateValue) => DateValueA 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 | |
isMonthDisabled | MatcherA function that returns whether or not a month is disabled | |
isMonthUnavailable | MatcherA function that returns whether or not a month is unavailable | |
isYearDisabled | MatcherA function that returns whether or not a year is disabled | |
isYearUnavailable | MatcherA function that returns whether or not a year is unavailable | |
modelValue | FormattedValue<R, M, V> | |
clearable | boolean | |
monthControls | booleanShow month controls | |
yearControls | booleanShow year controls | |
weekNumbers | boolean | |
allowNonContiguousRanges | booleanWhen combined with | |
pagedNavigation | booleanThis property causes the previous and next buttons to navigate by the number of months displayed at once, rather than one month | |
preventDeselect | booleanWhether or not to prevent the user from deselecting a date without selecting another date first | |
fixedWeeks | booleanWhether or not to always display 6 weeks in the calendar | |
disabled | booleanWhether or not the calendar is disabled | |
readonly | booleanWhether or not the calendar is readonly | |
initialFocus | booleanIf 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 | |
disableDaysOutsideCurrentView | booleanWhether or not to disable days outside the current view. | |
ui | Record<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; } |
content | SlotProps<P> |
heading | { value: string; view: CalendarView; date: DateValue; setView: (view: CalendarView) => void; setPlaceholder: (date: DateValue) => void; } |
day | Pick<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
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
}
}
}
})