Skip to content

Date Picker

A neo-brutalist style date picker component family built on v-calendar and reka-ui Popover. It provides 7 components covering various date selection scenarios, all sharing unified style variants, internationalization, and accessibility support.

Demo

Preview

DatePicker 单日期选择

未选择

DatePickerRange 日期范围

DateTimePicker 日期时间

TimePicker 纯时间

:
:

WeekPicker 周选择

MonthPicker 月份

YearPicker 年份

程序化控制(通过 ref 打开面板)

通过 pickerRef.open 可在外部按钮中打开或关闭日期面板。

Installation

pnpm dlx brutx-vue@latest add date-picker

Additional dependency required:

bash
pnpm add v-calendar

Usage

DatePicker - Single Date Selection

vue
<script setup>
import { ref } from 'vue'
import { DatePicker } from 'brutx-ui-vue/date-picker'

const date = ref(null)
</script>

<template>
    <DatePicker v-model="date" placeholder="Select date" />
</template>

With Shortcuts

vue
<script setup>
import { ref } from 'vue'
import { DatePicker } from 'brutx-ui-vue/date-picker'

const date = ref(null)

const shortcuts = [
    { label: 'Today', value: () => new Date() },
    { label: 'Tomorrow', value: () => {
        const d = new Date()
        d.setDate(d.getDate() + 1)
        return d
    }},
    { label: 'In a week', value: () => {
        const d = new Date()
        d.setDate(d.getDate() + 7)
        return d
    }},
]
</script>

<template>
    <DatePicker v-model="date" :shortcuts="shortcuts" :clearable="true" />
</template>

DatePickerRange - Date Range Selection

vue
<script setup>
import { ref } from 'vue'
import { DatePickerRange } from 'brutx-ui-vue/date-picker'

const dateRange = ref(null)
</script>

<template>
    <DatePickerRange
        v-model="dateRange"
        start-placeholder="Start date"
        end-placeholder="End date"
    />
</template>

DateTimePicker - Date Time Selection

vue
<script setup>
import { ref } from 'vue'
import { DateTimePicker } from 'brutx-ui-vue/date-picker'

const dateTime = ref(null)
</script>

<template>
    <DateTimePicker
        v-model="dateTime"
        placeholder="Select date and time"
        :show-seconds="true"
    />
</template>

DateTimePicker supports time step configuration:

vue
<template>
    <DateTimePicker
        v-model="dateTime"
        :time-step="{ hour: 2, minute: 15, second: 10 }"
    />
</template>

TimePicker - Time Only Selection

vue
<script setup>
import { ref } from 'vue'
import { TimePicker } from 'brutx-ui-vue/date-picker'

const time = ref(null)
</script>

<template>
    <TimePicker v-model="time" :show-seconds="true" />
</template>

WeekPicker - Week Selection

vue
<script setup>
import { ref } from 'vue'
import { WeekPicker } from 'brutx-ui-vue/date-picker'

const week = ref(null)
</script>

<template>
    <WeekPicker v-model="week" :week-starts-on="1" placeholder="Select week" />
</template>

weekStartsOn: 0 = week starts on Sunday, 1 = week starts on Monday (default). After selecting any date, modelValue automatically aligns to the start of that week, and the entire week is highlighted.

MonthPicker - Month Selection

vue
<script setup>
import { ref } from 'vue'
import { MonthPicker } from 'brutx-ui-vue/date-picker'

const month = ref(null)
</script>

<template>
    <MonthPicker v-model="month" placeholder="Select month" />
</template>

YearPicker - Year Selection

vue
<script setup>
import { ref } from 'vue'
import { YearPicker } from 'brutx-ui-vue/date-picker'

const year = ref(null)
</script>

<template>
    <YearPicker v-model="year" placeholder="Select year" />
</template>

Disabled and Read-only

vue
<template>
    <DatePicker v-model="date" disabled />
    <DatePicker v-model="date" readonly />
</template>

Date Range Constraints

vue
<script setup>
import { ref } from 'vue'
import { DatePicker } from 'brutx-ui-vue/date-picker'

const date = ref(null)
const minDate = new Date(2026, 0, 1)
const maxDate = new Date(2026, 11, 31)
</script>

<template>
    <DatePicker v-model="date" :min-date="minDate" :max-date="maxDate" />
</template>

Custom Display Format

Supports YYYY, YY, MM, DD, HH, mm, ss, WW (ISO week number) tokens:

vue
<template>
    <DatePicker v-model="date" display-format="YYYY/MM/DD" />
    <DateTimePicker v-model="dt" display-format="YYYY-MM-DD HH:mm:ss" />
    <WeekPicker v-model="week" display-format="YYYY-WW" />
    <YearPicker v-model="year" display-format="YY" />
</template>

displayFormat only affects the string shown in the input. It does not change the types of modelValue, minDate, maxDate, shortcuts, or emitted payloads; those public APIs always use Date, [Date, Date], or null. For cross-time-zone datetime consistency, normalize values in your application as Date, timestamps, or full ISO strings with explicit offsets before converting them to Date; do not rely on native parsing of arbitrary date strings.

Sub-components

ComponentPurpose
DatePickerSingle date selection
DatePickerRangeDate range selection (start and end dates)
DateTimePickerDate + time selection
TimePickerTime only selection (hours/minutes/seconds)
WeekPickerWeek selection (full week highlight)
MonthPickerMonth selection
YearPickerYear selection

Data Types

typescript
// Single date shortcut
interface DatePickerShortcut {
    label: string
    value: Date | (() => Date)
}

// Date range shortcut
interface DatePickerRangeShortcut {
    label: string
    value: [Date, Date] | (() => [Date, Date])
}

Composables

The logic for popup panel triggering, display formatting, clearing, and confirmation in components like DatePicker has been extracted into a standalone useDatePicker composable. It can be used independently when you need to build a fully custom trigger or calendar panel. It manages the panel open/close state, synchronizes the display value with modelValue, and triggers open / close / change / update:modelValue events through the provided emit.

ts
import { useDatePicker } from 'brutx-ui-vue/useDatePicker'
import type { UseDatePickerOptions } from 'brutx-ui-vue/useDatePicker'

const emit = defineEmits<{
    'update:modelValue': [value: Date | null]
    'change': [value: Date | null]
    'open': []
    'close': []
}>()

const {
    open,                  // Whether the panel is open
    displayValue,          // Current displayed value in the panel (temporary value before confirmation)
    formattedDisplay,      // Formatted display string
    handlePanelUpdate,     // Panel value update callback
    handlePanelConfirm,    // Panel confirm callback
    handlePanelClear,      // Panel clear callback
    handleClearClick,      // Trigger clear button click callback
    handleTriggerKeydown,  // Trigger keyboard event callback
} = useDatePicker({
    modelValue,
    displayFormat: 'YYYY-MM-DD',
    disabled: false,
    readonly: false,
    emit,
})

UseDatePickerOptions

PropTypeDefaultDescription
modelValueMaybeRefOrGetter<Date | null>nullCurrently selected date (supports v-model)
displayFormatMaybeRefOrGetter<string>'YYYY-MM-DD'Display format (supports YYYY, MM, DD, HH, mm, ss, WW tokens)
disabledMaybeRefOrGetter<boolean>falseWhether disabled
readonlyMaybeRefOrGetter<boolean>falseWhether read-only
emitDatePickerEmitEvent emission function (required, type consistent with component emits)

Return Values

PropTypeDescription
openRef<boolean>Whether the panel is open
displayValueRef<Date | null>Current displayed value in the panel
formattedDisplayComputedRef<string>Formatted string according to displayFormat
handlePanelUpdate(value)(value: Date | null) => voidCalled when the panel value updates, synchronizes displayValue and triggers update:modelValue
handlePanelConfirm(value)(value: Date | null) => voidCalled when the panel is confirmed, triggers update:modelValue / change and closes the panel
handlePanelClear()() => voidCalled when the panel is cleared, triggers update:modelValue(null) / change(null)
handleClearClick(event)(event: MouseEvent) => voidTrigger clear button click callback, prevents event propagation and clears the value
handleTriggerKeydown(event)(event: KeyboardEvent) => voidTrigger keyboard event callback, Enter / Space opens the panel

Note: emit must be a function conforming to the DatePickerEmit signature (i.e., the return value of component defineEmits). useDatePicker does not automatically manage onMounted / onUnmounted side effects and can be called at any time.

Programmatic Control

DatePicker, DateTimePicker, WeekPicker, MonthPicker, and YearPicker expose an open reactive ref via defineExpose, allowing parent components to programmatically open or close the date panel. open is a Ref<boolean> that is two-way bound with the internal Popover, and can be read and written directly.

Note: DatePickerRange and TimePicker do not expose open. DatePicker, DateTimePicker, WeekPicker, MonthPicker, and YearPicker also support v-model:open two-way binding. Using v-model:open is recommended over directly manipulating the ref.

vue
<script setup>
import { ref } from 'vue'
import { DatePicker } from 'brutx-ui-vue/date-picker'

const pickerRef = ref()
const date = ref(null)
</script>

<template>
    <DatePicker ref="pickerRef" v-model="date" />

    <button @click="pickerRef?.open = true">Open Panel</button>
    <button @click="pickerRef?.open = false">Close Panel</button>
</template>

Methods

Method/PropertyTypeDescription
openRef<boolean>Panel open/close state, readable and writable; set to true to open, false to close

Props

DatePicker

PropTypeDefaultDescription
modelValueDate | nullnullSelected date, supports v-model
openbooleanPanel open state, supports v-model:open two-way binding
displayFormatstring'YYYY-MM-DD'Display format (supports YYYY, YY, MM, DD, HH, mm, ss, WW tokens)
placeholderstringPlaceholder text
minDateDateMinimum selectable date
maxDateDateMaximum selectable date
disabledbooleanfalseDisabled state
readonlybooleanfalseRead-only state
clearablebooleanfalseWhether clearable
size'sm' | 'default' | 'lg''default'Input size
variant'default' | 'error' | 'success''default'Input variant
shortcutsDatePickerShortcut[][]Shortcut options
namestringForm field name
idstringComponent ID
ariaLabelstringAccessibility label

DatePickerRange

PropTypeDefaultDescription
modelValue[Date, Date] | nullnullSelected date range
displayFormatstring'YYYY-MM-DD'Display format (supports YYYY, YY, MM, DD, HH, mm, ss, WW tokens)
startPlaceholderstringStart date placeholder
endPlaceholderstringEnd date placeholder
separatorstringSeparator
minDateDateMinimum selectable date
maxDateDateMaximum selectable date
disabledbooleanfalseDisabled state
clearablebooleanfalseWhether clearable
size'sm' | 'default' | 'lg''default'Input size
variant'default' | 'error' | 'success''default'Input variant
shortcutsDatePickerRangeShortcut[][]Shortcut options
namestringForm field name
idstringComponent ID
ariaLabelstringAccessibility label

DateTimePicker

PropTypeDefaultDescription
modelValueDate | nullnullSelected date and time
openbooleanPanel open state, supports v-model:open two-way binding
displayFormatstring'YYYY-MM-DD HH:mm'Display format; defaults to 'YYYY-MM-DD HH:mm:ss' when showSeconds is true
showSecondsbooleanfalseWhether to show seconds
timeStep{ hour?: number; minute?: number; second?: number }{ hour: 1, minute: 1, second: 1 }Time step
placeholderstringPlaceholder text
minDateDateMinimum selectable date
maxDateDateMaximum selectable date
disabledbooleanfalseDisabled state
readonlybooleanfalseRead-only state
clearablebooleanfalseWhether clearable
size'sm' | 'default' | 'lg''default'Input size
variant'default' | 'error' | 'success''default'Input variant
shortcutsDatePickerShortcut[][]Shortcut options
namestringForm field name
idstringComponent ID
ariaLabelstringAccessibility label

TimePicker

Note: TimePicker is a pure time selector based on the Select component. It does not use a Popover popup panel, so it does not support Popover-related props such as open, readonly, clearable, size, variant, shortcuts, minDate, maxDate, and displayFormat.

PropTypeDefaultDescription
modelValueDate | nullnullSelected time
showSecondsbooleanfalseWhether to show seconds
timeStep{ hour?: number; minute?: number; second?: number }{ hour: 1, minute: 1, second: 1 }Time step
disabledbooleanfalseDisabled state
embeddedbooleanfalseWhether to render in embedded mode (no outer border)
ariaLabelstringAccessibility label

WeekPicker

PropTypeDefaultDescription
modelValueDate | nullnullSelected week (aligned to week start date)
openbooleanPanel open state, supports v-model:open two-way binding
displayFormatstring'YYYY-WW'Display format (supports YYYY, YY, MM, DD, HH, mm, ss, WW tokens)
weekStartsOn0 | 11Week start day (0=Sunday, 1=Monday)
placeholderstringPlaceholder text
minDateDateMinimum selectable date
maxDateDateMaximum selectable date
disabledbooleanfalseDisabled state
readonlybooleanfalseRead-only state
clearablebooleanfalseWhether clearable
size'sm' | 'default' | 'lg''default'Input size
variant'default' | 'error' | 'success''default'Input variant
shortcutsDatePickerShortcut[][]Shortcut options
namestringForm field name
idstringComponent ID
ariaLabelstringAccessibility label

MonthPicker

PropTypeDefaultDescription
modelValueDate | nullnullSelected month
openbooleanPanel open state, supports v-model:open two-way binding
displayFormatstring'YYYY-MM'Display format (supports YYYY, YY, MM tokens)
placeholderstringPlaceholder text
minDateDateMinimum selectable date
maxDateDateMaximum selectable date
disabledbooleanfalseDisabled state
readonlybooleanfalseRead-only state
clearablebooleanfalseWhether clearable
size'sm' | 'default' | 'lg''default'Input size
variant'default' | 'error' | 'success''default'Input variant
namestringForm field name
idstringComponent ID
ariaLabelstringAccessibility label

YearPicker

PropTypeDefaultDescription
modelValueDate | nullnullSelected year
openbooleanPanel open state, supports v-model:open two-way binding
displayFormatstring'YYYY'Display format (supports YYYY, YY tokens)
placeholderstringPlaceholder text
minDateDateMinimum selectable date
maxDateDateMaximum selectable date
disabledbooleanfalseDisabled state
readonlybooleanfalseRead-only state
clearablebooleanfalseWhether clearable
size'sm' | 'default' | 'lg''default'Input size
variant'default' | 'error' | 'success''default'Input variant
namestringForm field name
idstringComponent ID
ariaLabelstringAccessibility label

Events

EventPayloadDescription
update:modelValueDate | [Date, Date] | nullEmitted when the value changes, applies to all components
changeDate | [Date, Date] | nullEmitted when the panel closes and the value has changed, applies to all components except TimePicker
openEmitted when the panel opens, applies to all components except TimePicker
closeEmitted when the panel closes, applies to all components except TimePicker
update:openbooleanEmitted when the panel open state changes, used with v-model:open, applies to DatePicker, DateTimePicker, WeekPicker, MonthPicker, and YearPicker

Accessibility

Keyboard Navigation

KeyAction
Enter / SpaceOpen the panel
EscapeClose the panel
TabSwitch between the input and the panel

FAQ

Q: After installation, the component reports an error that the v-calendar module cannot be found?

A: The DatePicker component depends on v-calendar, which is an additional dependency that needs to be installed manually: pnpm add v-calendar. After installation, restart the dev server and it should work normally.

Q: Why doesn't DatePickerRange have an open prop?

A: DatePickerRange and TimePicker do not expose the open reactive ref and do not support controlling the panel state via v-model:open two-way binding. If you need programmatic control of the panel, please use DatePicker, DateTimePicker, WeekPicker, MonthPicker, or YearPicker.

Q: After selecting a date in WeekPicker, why does the displayed week start date not match expectations?

A: The weekStartsOn prop of WeekPicker controls the week start day: 0 means the week starts on Sunday, 1 means the week starts on Monday (default). After selecting any date, modelValue automatically aligns to the start of that week. If the displayed result does not match expectations, please check whether the weekStartsOn setting meets your business requirements.

Brute force builds.