@rozie-ui/date-picker-vue
| Install | |
|---|---|
npm install @rozie-ui/date-picker-vue |
|
| Latest Version: | 0.1.11 |
| Vue: | 3 |
| License: | MIT |
| Last Updated: | Oct 15, 2026 |
| Links: | GitHub · npm |
@rozie-ui/date-picker-vue
Idiomatic vue DatePicker — a headless, fully-accessible (WAI-ARIA) single-date calendar (a month grid with leading/trailing spill, prev/next month navigation, weekStartsOn rotation, min/max/disabledDates gating, roving keyboard focus, and localized Intl labels) compiled from one Rozie source. It is HEADLESS: accept the default token-themed calendar, or override the month-nav header via the scoped slot. Every visual value is a CSS custom property, so it re-skins to any design system. This package is generated; do not edit src/ by hand.
Install
npm i @rozie-ui/date-picker-vue
Peer dependencies: vue. Install them alongside this package.
Also installed: @rozie/runtime-vue — Rozie's small, tree-shaken runtime helper package (controllable state, keyboard navigation, event modifiers, and safe interpolation). It arrives as a regular dependency, so npm pulls it for you. Your bundler keeps only the helpers this component actually uses — typically a few hundred bytes to a few KB, minified and gzipped. What's in it and what it costs.
Usage
<script setup lang="ts">
import { ref } from 'vue';
import DatePicker from '@rozie-ui/date-picker-vue';
const date = ref(''); // ISO YYYY-MM-DD, '' = no selection
function onChange(e: { value: string }) {
console.log('picked:', e.value);
}
// Range mode: value becomes a { start, end } object.
const range = ref({ start: '', end: '' });
const iso = (d: Date) => d.toISOString().slice(0, 10);
const presetRanges = [
{ label: 'Q1 2026', range: { start: '2026-01-01', end: '2026-03-31' } },
// a () => { start, end } thunk is resolved fresh on render
{ label: 'Last 7 days', range: () => ({ start: iso(new Date(Date.now() - 6 * 864e5)), end: iso(new Date()) }) },
];
</script>
<template>
<DatePicker v-model:value="date" min="2026-01-01" @change="onChange" />
<!-- Custom header via the scoped #header slot -->
<DatePicker v-model:value="date">
<template #header="{ label, prev, next }">
<div class="my-header">
<button @click="prev">Prev</button>
<strong>{{ label }}</strong>
<button @click="next">Next</button>
</div>
</template>
</DatePicker>
<!-- Range selection with quick-pick presets + a #presets override -->
<DatePicker
selectionMode="range"
v-model:value="range"
:presetRanges="presetRanges"
@rangeComplete="(e) => console.log('range:', e.value)"
>
<template #presets="{ presets, apply }">
<button v-for="p in presets" :key="p.label" @click="apply(p.range)">{{ p.label }}</button>
</template>
</DatePicker>
<!-- showFooter renders a default Today / Clear row; #footer overrides it -->
<DatePicker v-model:value="date" show-footer />
</template>
Theming
Every visual value is a --rozie-datepicker-* CSS custom property — override any of them at any ancestor scope. Ready-made design-system bridges ship in the package:
import '@rozie-ui/date-picker-vue/themes/shadcn.css'; // or material.css, bootstrap.css, base.css
Props
| Name | Type | Default | Two-way (model) | Required | Description |
|---|---|---|---|---|---|
value |
any |
'' |
✓ | The selected value (two-way r-model). Polymorphic on selectionMode: in single mode an ISO YYYY-MM-DD string ("" = nothing selected); in range mode a { start, end } object of ISO endpoints ("" = an unset endpoint). As the sole model: true prop it drives the Angular ControlValueAccessor, so a DatePicker is a form control ([(ngModel)] / [formControl] bind directly). Selecting a day writes the new value back and emits change. Lit caveat (range mode): the object form must be delivered via a property binding (.value=${obj} / r-model), never a string value="..." attribute — the same rule already in force for disabledDates. |
|
selectionMode |
String |
"single" |
Selection mode: 'single' (the default — value is one ISO YYYY-MM-DD string, fully backward-compatible) or 'range' (value becomes a { start, end } object selected with two clicks plus a live hover preview, direction-agnostic). In range mode a completed selection additionally emits rangeComplete. |
||
min |
String |
null |
Inclusive lower bound as an ISO YYYY-MM-DD string. Days before it are rendered disabled and cannot be selected or focused. null (the default) imposes no lower bound. |
||
max |
String |
null |
Inclusive upper bound as an ISO YYYY-MM-DD string. Days after it are rendered disabled and cannot be selected or focused. null (the default) imposes no upper bound. |
||
disabledDates |
Array |
[] |
An array of ISO YYYY-MM-DD strings to disable individually (e.g. holidays or already-booked days), in addition to the min/max bounds. Disabled days are non-interactive and marked aria-disabled. |
||
weekStartsOn |
Number |
0 |
The first day of the week as a number, 0 = Sunday through 6 = Saturday. Rotates both the weekday header row and the grid columns (e.g. 1 for a Monday-first calendar). |
||
disabled |
Boolean |
false |
Disable the entire control — every day cell and the previous/next month buttons become non-interactive and are marked aria-disabled. Also sets the Angular ControlValueAccessor disabled state. |
||
locale |
String |
"en-US" |
BCP-47 locale tag used by Intl.DateTimeFormat to render the month-year heading and the short weekday header labels (e.g. "fr-FR", "ja-JP"). Falls back to English names in a runtime without Intl. |
||
labels |
Object |
{} |
Optional overrides for the 10 static English "chrome" strings, keyed by root, previousMonth, nextMonth, changeMonthYear, changeYear, chooseMonth, chooseYear, presets, today, clear (defaults: "Date picker", "Previous month", "Next month", "Change month and year", "Change year", "Choose month", "Choose year", "Date range presets", "Today", "Clear"). Honest split: Intl is a date/number formatter, not a message catalog — it can localize a DATE but cannot translate the phrase "Previous month". The day-cell accessible name, each multi-month panel's own grid caption, the weekday header long names, and the month-year heading text are already Intl-derived from the locale prop and are NOT labels keys; the 10 chrome phrases above are English-static and only labels can translate them. An empty object (the default) yields the English defaults with zero config. Lit caveat: pass via a property binding (.labels=${…}), never a string attribute — the same rule already in force for disabledDates/presetRanges. |
||
presetRanges |
Array |
[] |
Quick-pick presets for range mode — an array of { label, range } where range is a literal { start, end } value or a () => { start, end } thunk (the consumer owns the date math and i18n labels). Renders a default preset rail beneath the grid; the #presets slot overrides it. Lit caveat: pass via a property binding (.presetRanges=${[…]}) — thunks inside the array cannot survive a string attribute, same as disabledDates. |
||
monthYearNav |
Boolean |
true |
Render the month-year heading as a clickable drill button that navigates days → months → years (and a year label that drills months → years). Capability-on: this is the documented exception to the boolean-default-false rule — the drill navigation is the ergonomic win of this feature, so it defaults to true. Set :month-year-nav="false" to restore the static heading <span> (byte-identical to the pre-navigation output). |
||
numberOfMonths |
Number |
1 |
How many month grids to render side by side, anchored at the view month and stepping forward (e.g. 2 for a two-up range calendar). 1 (the default) emits exactly the single-month markup with no extra wrapper element. |
||
showFooter |
Boolean |
false |
Render a Today / Clear footer row beneath the calendar grid. Today selects (single mode) or navigates to (range mode) the current date; Clear deselects. The #footer slot fully overrides the default row, receiving { today, clear, todayIso }. |
||
disabledDaysOfWeek |
Array |
[] |
An array of weekday indices to disable, Number[] where 0 = Sunday through 6 = Saturday (e.g. [0, 6] disables every weekend). Serializable, so it passes fine as a plain attribute. Threaded through the single gating funnel, so disabled weekdays are non-interactive, non-focusable, and marked aria-disabled — in agreement with day cells, drill enablement, and keyboard focus. |
||
isDateDisabled |
Function |
null |
A consumer predicate (iso: string) => boolean — return true to disable the given ISO YYYY-MM-DD date (e.g. custom holiday / blackout rules beyond disabledDates/min/max). Threaded through the single gating funnel so day cells, drill enablement, and focus all agree. Lit caveat: pass via a property binding (.isDateDisabled=${fn}), never a string attribute — a function cannot survive attribute serialization, the same rule already in force for disabledDates/presetRanges. |
Events
| Event | Description |
|---|---|
change |
Fired whenever the selected value changes — selecting a day, applying a preset, or a programmatic clear(). Payload { value } — the new value: in single mode the selected ISO YYYY-MM-DD string (or "" when cleared); in range mode the { start, end } object (an in-progress anchor is { start, end: "" }; cleared is { start: "", end: "" }). Not fired when the picked date equals the current selection. |
rangeComplete |
Range mode only. Fired when a range selection completes — the second endpoint lands (the two-click commit) or a preset is applied. Payload { value } — the ordered { start, end } object (start <= end). NOT fired on the first (anchor-only) click. Per-target consumer prop casing differs: React onRangeComplete, Vue @range-complete, Svelte onrangecomplete (lowercased), Angular (rangeComplete), Solid onRangeComplete, Lit range-complete (addEventListener — kebab-cased at dispatch, NOT case-preserved). |
Imperative handle
Beyond props, the component exposes imperative methods (declared once in the Rozie source via $expose). Grab a handle with the native ref mechanism and call them directly:
| Method | Description |
|---|---|
focus |
Move keyboard focus into the calendar grid — onto the selected day, else today (when in view), else the first visible day. Useful right after the picker becomes visible. |
goToToday |
Swing the displayed month to today's month without changing the selection (a view-only navigation). |
clear |
Deselect the current date (sets value to ""). Emits change with an empty value unless nothing is selected. |
<script setup>
import { ref } from 'vue';
const picker = ref(); // template ref
</script>
<template>
<DatePicker ref="picker" v-model:value="date" />
<button @click="picker.goToToday()">Today</button>
</template>
Slots
| Slot | Params |
|---|---|
| header | label, prev, next, disabled, openMonths, openYears, closeDrill, viewMode |
| footer | today, clear, todayIso |
| presets | presets, apply |
Related Packages
Idiomatic Vue headless WAI-ARIA combobox / autocomplete (text input + popup list...
Idiomatic Vue headless, accessible toast / notification host (queue, auto-dismis...
Idiomatic Vue headless WAI-ARIA data table (sorting, filtering, pagination, sele...
Idiomatic Vue headless WAI-ARIA one-time-code / PIN input (segmented cells, past...
Version History
| Version | Released | Vue | License |
|---|---|---|---|
| 0.1.11 | ^3.4 | MIT | |
| 0.1.10 | ^3.4 | MIT | |
| 0.1.9 | ^3.4 | MIT | |
| 0.1.8 | ^3.4 | MIT | |
| 0.1.7 | ^3.4 | MIT | |
| 0.1.6 | ^3.4 | MIT | |
| 0.1.5 | ^3.4 | MIT | |
| 0.1.4 | ^3.4 | MIT | |
| 0.1.3 | ^3.4 | MIT | |
| 0.1.2 | ^3.4 | MIT |