DatePicker
DatePicker is a calendar field with three modes — a single date, a range, or a datetime — built on reka-ui's popover. It localizes its month, weekday, and time labels through Intl, and each text label can be overridden per instance.
<script setup lang="ts">
import { ref } from 'vue'
import { DatePicker } from '@myghf/ui'
const date = ref<Date | null>(null)
</script>
<template>
<DatePicker v-model="date" />
</template>Examples
Single date
The trigger shows the localized date, or the placeholder while empty. Selecting a day commits it and closes the popover; a Clear action appears once a value exists.
<script setup lang="ts">
import { ref } from 'vue'
import { DatePicker } from '@myghf/ui'
const date = ref<Date | null>(new Date(2026, 8, 27))
</script>
<template>
<div class="grid max-w-xs gap-4">
<div class="grid gap-1.5">
<label class="text-sm font-medium text-foreground" for="demo-date">Appointment date</label>
<DatePicker input-id="demo-date" v-model="date" />
</div>
<DatePicker :model-value="null" placeholder="Disabled" disabled />
</div>
</template>Range and date-time modes
mode="range" returns a [start, end] tuple (ordered, regardless of click order). mode="datetime" adds an hour/minute picker and commits only when Apply is pressed; hourFormat, minuteStep, and locale shape that picker.
<script setup lang="ts">
import { ref } from 'vue'
import { DatePicker } from '@myghf/ui'
const admission = ref<[Date, Date] | null>(null)
const appointment = ref<Date | null>(null)
</script>
<template>
<div class="grid max-w-sm gap-4">
<DatePicker v-model="admission" mode="range" placeholder="Admission – discharge" />
<DatePicker
v-model="appointment"
mode="datetime"
hour-format="12"
:minute-step="15"
locale="en-US"
placeholder="Appointment date and time"
/>
</div>
</template>Props
| Prop | Type | Default | Description |
|---|---|---|---|
mode | 'date' | 'range' | 'datetime' | 'date' | Picker behaviour: single day, a start–end range, or a day plus time. |
modelValue | Date | [Date, Date] | null | — | Selected value: a Date for date/datetime, a [start, end] tuple for range, null when empty. |
placeholder | string | — | Trigger text while empty. Deprecated — prefer labels.placeholder; a labels.placeholder value wins over it. |
disabled | boolean | false | Disables the trigger. |
invalid | boolean | false | Applies the error border. Visual only; it does not set aria-invalid. |
size | 'sm' | 'default' | 'default' | Trigger height. |
inputId | string | — | Applied to the trigger button so an external <label for> matches. |
hourFormat | '12' | '24' | '24' | Time format in datetime mode. '12' also renders an AM/PM control. |
minuteStep | number | 1 | Minute interval in datetime mode; clamped to 1–30. |
locale | string | runtime locale | BCP-47 locale for month, weekday, and time formatting. Resolved from Intl.DateTimeFormat at setup, falling back to 'en'. |
labels | Partial<DatePickerLabels> | — | Per-instance text overrides; merged over the locale defaults. |
weekStartsOn | number | from locale | First day of the week (0 = Sunday … 6 = Saturday). Defaults to the locale's week info, falling back to Monday. |
defaultOpen | boolean | false | Opens the popover on mount. Useful for demos and tests. |
labels keys
labels accepts any subset of: placeholder, previousMonth, nextMonth, clear, apply, today, time, hour, minute, am, pm. Built-in translations ship for en, fr, es, de, and ar; other locales fall back to English. today is part of the type for parity but is not currently rendered by the component.
Events
| Event | Payload | Description |
|---|---|---|
update:modelValue | Date | [Date, Date] | null | Emitted on selection. range waits for both ends; datetime waits for Apply; Clear emits null. |
Slots
Every label has a matching slot, each taking no props. A non-empty slot's text wins over both labels and the locale default:
| Slot | Description |
|---|---|
label-placeholder | Trigger text while empty. |
label-previousMonth | Accessible name of the previous-month button. |
label-nextMonth | Accessible name of the next-month button. |
label-clear | Clear action text. |
label-apply | Apply action text (datetime mode). |
label-today | Reserved; not currently rendered. |
label-time | Accessible name of the time group and meridiem select. |
label-hour | Accessible name of the hour select. |
label-minute | Accessible name of the minute select. |
label-am / label-pm | AM / PM option text (hourFormat="12"). |
Exposed methods
None. DatePicker does not call defineExpose.
Accessibility
- The trigger is a real
<button>with popup semantics from reka-ui (aria-haspopup,aria-expanded); the popover manages focus and closes on Esc. - Previous/next month buttons carry
aria-labels frompreviousMonth/nextMonth, and the time selects are labelled fromtime/hour/minute. - Day cells are announced as bare numbers. Each day is a button whose text is just the day of the month, with no full-date
aria-label, so a screen reader reads "27" rather than "27 September 2026". This is a known gap — supply surrounding context or an external readout if the full date must be announced. invalidonly changes colour; it does not setaria-invalid. Add it to the trigger via a fall-through attribute if needed.- For an external
<label for>, passinputIdto match the label'sfor(the trigger is a button; associating viaaria-labelledbyis also acceptable).
Dark mode & RTL
- The popover and trigger use semantic tokens (
bg-surface,text-foreground,border-border,shadow-popover). The in-range days useprimary-100/primary-800with explicitdark:bg-primary-900/40 dark:text-primary-200variants, and the selected endpoints useprimary-500with white text, so both themes stay legible. - The navigation chevrons use
rtl:rotate-180, so "previous" and "next" point the correct way under RTL. The calendar grid and time controls are direction-neutral.