Skip to content

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.

vue
<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.

vue
<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.

vue
<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 ​

PropTypeDefaultDescription
mode'date' | 'range' | 'datetime''date'Picker behaviour: single day, a start–end range, or a day plus time.
modelValueDate | [Date, Date] | null—Selected value: a Date for date/datetime, a [start, end] tuple for range, null when empty.
placeholderstring—Trigger text while empty. Deprecated — prefer labels.placeholder; a labels.placeholder value wins over it.
disabledbooleanfalseDisables the trigger.
invalidbooleanfalseApplies the error border. Visual only; it does not set aria-invalid.
size'sm' | 'default''default'Trigger height.
inputIdstring—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.
minuteStepnumber1Minute interval in datetime mode; clamped to 1–30.
localestringruntime localeBCP-47 locale for month, weekday, and time formatting. Resolved from Intl.DateTimeFormat at setup, falling back to 'en'.
labelsPartial<DatePickerLabels>—Per-instance text overrides; merged over the locale defaults.
weekStartsOnnumberfrom localeFirst day of the week (0 = Sunday … 6 = Saturday). Defaults to the locale's week info, falling back to Monday.
defaultOpenbooleanfalseOpens 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 ​

EventPayloadDescription
update:modelValueDate | [Date, Date] | nullEmitted 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:

SlotDescription
label-placeholderTrigger text while empty.
label-previousMonthAccessible name of the previous-month button.
label-nextMonthAccessible name of the next-month button.
label-clearClear action text.
label-applyApply action text (datetime mode).
label-todayReserved; not currently rendered.
label-timeAccessible name of the time group and meridiem select.
label-hourAccessible name of the hour select.
label-minuteAccessible name of the minute select.
label-am / label-pmAM / 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 from previousMonth / nextMonth, and the time selects are labelled from time / 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.
  • invalid only changes colour; it does not set aria-invalid. Add it to the trigger via a fall-through attribute if needed.
  • For an external <label for>, pass inputId to match the label's for (the trigger is a button; associating via aria-labelledby is also acceptable).

Dark mode & RTL ​

  • The popover and trigger use semantic tokens (bg-surface, text-foreground, border-border, shadow-popover). The in-range days use primary-100 / primary-800 with explicit dark:bg-primary-900/40 dark:text-primary-200 variants, and the selected endpoints use primary-500 with 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.

Released under the MIT License.