@ignatremizov/vue-tailwind-datepicker
v2.1.3
Published
A Vue 3 datepicker component using Tailwind CSS and Day.js
Downloads
383
Maintainers
Readme
Vue Tailwind Datepicker
Highlights
Documentation
Go to full documentation or run npm run docs:dev locally.
Project guidelines:
Installation
⚠️ Vue Tailwind Datepicker uses Tailwind CSS (with the @tailwindcss/forms plugin) & Day.js under the hood, you must install those packages before. See the repository docs for complete setup guides.
Install via npm
npm install @ignatremizov/vue-tailwind-datepickerInstall via yarn
yarn add @ignatremizov/vue-tailwind-datepickerSimple Usage
How it works,
<script setup>
import { ref } from 'vue'
import VueTailwindDatepicker from '@ignatremizov/vue-tailwind-datepicker'
const dateValue = ref([])
const formatter = ref({
date: 'DD MMM YYYY',
month: 'MMM',
})
</script>
<template>
<div>
<VueTailwindDatepicker v-model="dateValue" :formatter="formatter" />
</div>
</template>Time Picker Modes
Use timePickerStyle to control whether time is disabled, text input, or wheel UI.
timePickerStyle="none"(default): date-only mode.timePickerStyle="input": text input time under the calendar.timePickerStyle="wheel-inline": wheel controls always visible under the calendar.timePickerStyle="wheel-page": calendar and wheel shown as separate pages.timePageMode="toggle"(default): manual page switching.timePageMode="after-date": automatically switch from calendar to time after date selection.- For any non-
nonemode, commit requires explicit Apply even whenautoApply=true. formatter.dateremains the source of truth for date/time parse and format behavior.
Input mode
<script setup>
import { ref } from 'vue'
const value = ref('')
</script>
<template>
<vue-tailwind-datepicker
v-model="value"
as-single
time-picker-style="input"
:auto-apply="true"
:formatter="{ date: 'YYYY-MM-DD HH:mm', month: 'MMM' }"
/>
</template>Range with defaults
<script setup>
import { ref } from 'vue'
const value = ref({
startDate: '',
endDate: '',
})
</script>
<template>
<vue-tailwind-datepicker
v-model="value"
use-range
as-single
time-picker-style="wheel-inline"
default-time="09:00"
default-end-time="17:00"
:formatter="{ date: 'YYYY-MM-DD h:mm A', month: 'MMM' }"
/>
</template>Notes
- Existing date-only integrations do not need changes.
- If you enable a time mode (
input,wheel-inline,wheel-page), ensureformatter.dateincludes time tokens (HH:mm,HH:mm:ss, or 12-hour equivalents with meridiem). - If incoming model values do not include time, hydration uses
defaultTime/defaultEndTimethen falls back to00:00[:00].
Wheel modes
timePickerStyle="wheel-inline": native-like wheel selectors (hh/mm[/ss], plus meridiem in 12-hour mode), always visible.timePickerStyle="wheel-page"withtimePageMode="toggle": manual back-and-forth using the switch button.timePickerStyle="wheel-page"withtimePageMode="after-date": flow iscalendar -> time -> apply.timeWheelScrollMode="boundary"(default): wheels snap discretely at each unit.timeWheelScrollMode="fractional": wheels drift continuously based on lower-order units (for example hour drift by minute progress).
<vue-tailwind-datepicker
v-model="value"
as-single
time-picker-style="wheel-page"
time-page-mode="after-date"
time-wheel-scroll-mode="fractional"
:formatter="{ date: 'YYYY-MM-DD HH:mm', month: 'MMM' }"
/>Native Scroll Selector Mode
Enable native-like month/year scrolling with selectorMode (use :selector-mode in templates). Default is false.
Single date
<script setup>
import { ref } from 'vue'
const singleDate = ref('')
</script>
<template>
<vue-tailwind-datepicker v-model="singleDate" as-single :selector-mode="true" />
</template>Range
<script setup>
import { ref } from 'vue'
const rangeDate = ref({
startDate: '',
endDate: '',
})
</script>
<template>
<vue-tailwind-datepicker v-model="rangeDate" use-range :selector-mode="true" />
</template>Selector behavior options
selector-year-scroll-mode="boundary": clarity-first default; year wheel advances discretely.selector-year-scroll-mode="fractional": continuous month-synced year-wheel drift.:selector-year-home-jump="100": Home key jump size (years) in year wheel mode.:selector-year-end-jump="100": End key jump size (years) in year wheel mode.:selector-year-page-jump="10": PageUp/PageDown jump size (years) in year wheel mode.:selector-year-page-shift-jump="100": Shift+PageUp/Shift+PageDown jump size (years).:selector-focus-tint="false": keeps selector containers neutral while preserving functionality.:close-on-range-selection="false": keeps the popover open after selecting the second range date. Recommended withselector-modewhen you want a fully native-like keep-open flow. Inno-inputstatic mode this option is a no-op because there is no popover to close.
Popover behavior options
open-focus-target="input" | "calendar": controls where focus lands after popover open.:popover-transition="true|false": toggles enter/leave transition classes.:popover-restore-focus="false"(default): prevents automatic refocus of the input/trigger when the popover closes. Set:popover-restore-focus="true"to restore legacy trigger-refocus behavior.
<vue-tailwind-datepicker
v-model="rangeDate"
use-range
:selector-mode="true"
selector-year-scroll-mode="boundary"
:selector-year-home-jump="100"
:selector-year-end-jump="100"
:selector-year-page-jump="10"
:selector-year-page-shift-jump="100"
:selector-focus-tint="true"
:close-on-range-selection="false"
open-focus-target="input"
:popover-transition="true"
:popover-restore-focus="false"
/>Template ref API (stable integration)
For host flows that need imperative focus/readiness control, use the component ref API instead of querying internal .vtd-* selectors:
isReady(): booleanwaitForReady(options?): Promise<boolean>focusCalendar(options?): Promise<boolean>focusInput(options?): boolean
waitForReady is event-driven with a timeout fallback. Use timeoutMs (or legacy maxAttempts/retryDelayMs).
<script setup lang="ts">
import VueTailwindDatepicker from '@ignatremizov/vue-tailwind-datepicker'
import { ref } from 'vue'
type DatePickerRef = InstanceType<typeof VueTailwindDatepicker>
const pickerRef = ref<DatePickerRef | null>(null)
const rangeDate = ref({
startDate: '',
endDate: '',
})
async function focusCalendarAfterOpen() {
const ready = await pickerRef.value?.waitForReady({ timeoutMs: 400 })
if (ready)
await pickerRef.value?.focusCalendar({ preventScroll: true })
}
</script>
<template>
<VueTailwindDatepicker ref="pickerRef" v-model="rangeDate" use-range />
</template>Selector wheel visuals are also themeable through CSS variables on .vtd-datepicker (month/year selected and hover colors, borders, typography, wheel cell sizing, and shared selected/unselected wheel text tokens used by selector + time wheels). Calendar range preview colors/opacity are exposed via --vtd-calendar-range-preview-bg and --vtd-calendar-range-preview-bg-dark. See docs/theming-options.md for examples.
Shortcut Layout Customization
Shortcut sizing can be customized through the shortcut-item slot and host CSS.
<vue-tailwind-datepicker v-model="dateValue" :shortcuts="typedShortcuts">
<template #shortcut-item="{ id, label, isDisabled, disabledReason, activate }">
<button
type="button"
class="vtd-shortcuts w-[10.5rem] rounded border px-2 py-1.5 text-left text-sm"
:data-shortcut-id="id"
:disabled="isDisabled"
@click="activate"
>
{{ label }}
</button>
<small v-if="disabledReason === 'blocked-date'">blocked by disableDate</small>
</template>
</vue-tailwind-datepicker>- Control width/spacing/typography with slot classes.
- Use
.vtd-shortcutsfor host-level CSS overrides. - Use
disabledReason(explicit|blocked-date) to show user-facing status badges. - There is no dedicated
shortcutPanelWidthprop yet; panel width follows rendered shortcut content.
Weekend Day Styling Hooks
Day cells now expose stable weekend hooks so host apps can apply weekend tinting without patching component internals:
vtd-weekendfor Saturday and Sundayvtd-saturdayfor Saturday onlyvtd-sundayfor Sunday only
/* Base weekend tint */
.vtd-datepicker-date.vtd-weekend {
color: #dc2626;
}
/* Optional split palette */
.vtd-datepicker-date.vtd-saturday {
color: #ea580c;
}
.vtd-datepicker-date.vtd-sunday {
color: #b91c1c;
}Hooks are stable across locales and also apply when selector-mode is enabled:
<vue-tailwind-datepicker v-model="rangeEn" use-range i18n="en" />
<vue-tailwind-datepicker v-model="rangeDe" use-range i18n="de" />
<vue-tailwind-datepicker
v-model="rangeSelector"
use-range
as-single
:selector-mode="true"
i18n="en"
/>Hooks are additive: selected/range/disabled/today semantics remain unchanged unless your host CSS explicitly overrides them.
Highlighted Day Hooks
Use highlightDates to mark specific calendar days with a stable hook class.
<script setup>
import dayjs from 'dayjs'
import { ref } from 'vue'
const value = ref('2025-06-15 00:00:00')
const highlightedDates = [
new Date(2025, 5, 7),
'2025-06-15',
dayjs('2025-06-21'),
]
</script>
<template>
<vue-tailwind-datepicker
v-model="value"
as-single
:highlight-dates="highlightedDates"
/>
</template>highlightDatesaccepts an array ofDate,dayjs, or strings matching the configuredformatter.dateor its date-only form.- Matching is date-only (
YYYY-MM-DD), so time values are ignored. - Matching cells receive the
vtd-highlightedclass.
.vtd-datepicker-date.vtd-highlighted {
color: #0f766e;
}The hook is additive, so highlighted cells still keep their normal selected/range/disabled/today behavior unless your host CSS overrides it.
Direct Year Input (Selector Mode)
Use directYearInput to allow typing a year directly in selector mode. The feature is opt-in and defaults to false.
<script setup>
import { ref } from 'vue'
const value = ref({
startDate: '',
endDate: '',
})
</script>
<template>
<vue-tailwind-datepicker
v-model="value"
use-range
:selector-mode="true"
:direct-year-input="true"
year-numbering-mode="historical"
/>
</template>Behavior summary:
- Valid typed tokens commit immediately to selector/calendar state and emit
update:modelValuefor the active context. - Year parsing supports signed
-99999..99999;year-numbering-mode="historical"rejects0, whileastronomicalaccepts0. - Typeahead intentionally supports historical/fantasy/scientific timelines (not only modern Gregorian UI assumptions):
- Prefix
1anchors to1950for 1-digit starts. - Prefix
2anchors to the current year suffix for 1-digit starts. - 2-digit tokens use a mid-century suffix (
xx50). - 3-digit non-zero prefixes use a mid-decade suffix (
xxx5). - Up to 5 digits are accepted before the token resets.
+/-set explicit sign,Backspaceedits the active token, andEscapeclears it.- Typeahead state auto-resets after 900 ms of idle time.
- Prefix
- Enter confirms in place and keeps selector mode open.
- Escape and invalid blur revert to the last valid year text.
- In range mode, temporary
start > endfrom live typing is allowed and is normalized only at explicit persist boundaries (Applyor close-with-persist toggle). - Cancel-like exits (Escape, Cancel button, backdrop dismiss) do not trigger range normalization.
Theming options
Light Mode

Dark Mode

Local Dev Ports
npm run devuses Vite dev-server defaults (typicallyhttp://127.0.0.1:5173/unless overridden).npm run previewruns onhttp://127.0.0.1:4173/(configured inpackage.json).npm run docs:screenshotsaccepts a base URL argument, for example:npm run docs:screenshots -- http://127.0.0.1:5173/npm run docs:screenshots -- http://127.0.0.1:4180/(useful when working across multiple worktrees).- Screenshot quality is configurable via env vars:
DOC_SCREENSHOT_VIEWPORT_WIDTH(default1800)DOC_SCREENSHOT_VIEWPORT_HEIGHT(default1300)DOC_SCREENSHOT_DEVICE_SCALE(default2)DOC_SCREENSHOT_ZOOM(default1.0)
npm run docs:videosgenerates MP4 animation captures (same URL argument pattern):npm run docs:videos -- http://127.0.0.1:5173/npm run docs:videos -- http://127.0.0.1:4180/
Dependency Pins (Dev)
@headlessui/vueis pinned to a GitHub release tarball from the fork to include Vue fixes not yet available in the upstream npm line used by this repo.js-beautifyis pinned viaoverridesto a fork tarball commit to break an upstream dev-only audit chain (through@vue/test-utils), until upstream publishes an equivalent fix.- When updating either pin, prefer a tagged release or commit SHA URL and run:
npm installnpm run test:unitnpm run buildnpm audit
Changelog
All notable changes to this project will be documented in the Releases Page.
Funding
License
The MIT License. Please see for more information.
Contributors
- Ignat Remizov (maintainer of this fork)
- Alexandre Le Corre (upstream maintainer)
- Kenhyuwa (original author/contributor)
- See CONTRIBUTORS.md for contributor notes.
- Vue
- Tailwind CSS
- day.js
- and other support...
