Skip to content
RTL Support: Full
Accessibility: Full
Translations: Not Needed

Date Picker

A versatile date picker component that allows users to select dates, date ranges, months, and years with a modern, accessible interface. The component supports various formats, sizes, and customization options.

Basic Usage

A simple date picker with default settings:

vue
<template>
  <HLDatePicker id="date-picker-with-default" type="date" clearable />
</template>
<script setup lang="ts">
import { HLDatePicker } from '@platform-ui/highrise'
</script>

Controlled Value

Bind the selection with v-model:value. The value is a timestamp in milliseconds (or a [start, end] tuple for daterange).

Current value: Fri May 12 2023

vue
<template>
  <HLDatePicker type="date" clearable v-model:value="value" />
  <p>Current value: {{ value ? new Date(value).toDateString() : 'none' }}</p>
</template>
<script setup lang="ts">
import { HLDatePicker } from '@platform-ui/highrise'
import { ref } from 'vue'

// Timestamp in milliseconds
const value = ref<number | null>(1683849600000)
</script>

INFO

Pass a defaultValue (instead of value) when you want an uncontrolled picker that seeds an initial date but manages its own state afterward. Once value is set, defaultValue is ignored.

Disabled

Set disabled to make the picker read-only and non-interactive.

vue
<template>
  <HLDatePicker type="date" disabled :value="1183135260000" />
</template>
<script setup lang="ts">
import { HLDatePicker } from '@platform-ui/highrise'
</script>

With Custom Format

Format the date picker's display with the format prop (passed as a string). If you omit format, the picker uses a sensible default for the selected type. See Supported Formats for the full list of tested format strings.

vue
<template>
  <HLDatePicker id="date-picker-with-custom-format" type="date" format="dd - MM - yyyy" clearable />
</template>
<script setup lang="ts">
import { HLDatePicker } from '@platform-ui/highrise'
</script>

<!-- Output example: 15 - 05 - 2023 -->

With CTA buttons

Show confirm/clear action buttons in the panel with the showCTA prop.

vue
<template>
  <HLDatePicker id="date-picker-with-cta" type="date" clearable showCTA />
</template>
<script setup lang="ts">
import { HLDatePicker } from '@platform-ui/highrise'
</script>

Emit on Every Selection

By default, when showCTA is enabled the value only commits when the user clicks Confirm. Set :updateValueOnConfirm="false" to emit @update:value immediately on every selection while still showing the action buttons.

Live updated value: none

vue
<template>
  <p>Live updated value: {{ value ? new Date(value).toDateString() : 'none' }}</p>
  <HLDatePicker type="date" showCTA :updateValueOnConfirm="false" v-model:value="value" />
</template>
<script setup lang="ts">
import { HLDatePicker } from '@platform-ui/highrise'
import { ref } from 'vue'

const value = ref<number | null>(null)
</script>

INFO

updateValueOnConfirm has no effect when showCTA is false — without the action buttons, selections always emit immediately.

All Sizes

The date picker is available in six sizes, controlled by the size prop.

vue
<template>
  <HLDatePicker id="date-picker-size-lg" size="lg" />
  <HLDatePicker id="date-picker-size-md" size="md" />
  <HLDatePicker id="date-picker-size-sm" size="sm" />
  <HLDatePicker id="date-picker-size-xs" size="xs" />
  <HLDatePicker id="date-picker-size-2xs" size="2xs" />
  <HLDatePicker id="date-picker-size-3xs" size="3xs" />
</template>
<script setup lang="ts">
import { HLDatePicker } from '@platform-ui/highrise'
</script>

Month Picker

Select a month and year with type="month".

vue
<template>
  <HLDatePicker id="date-picker-with-month-picker" type="month" clearable format="MM/yyyy" />
</template>
<script setup lang="ts">
import { HLDatePicker } from '@platform-ui/highrise'
</script>

Year Picker

Year Grid

Display years in a grid layout with the yearGrid prop.

vue
<template>
  <HLDatePicker id="date-picker-with-year-grid" type="year" clearable yearGrid />
</template>
<script setup lang="ts">
import { HLDatePicker } from '@platform-ui/highrise'
</script>

Year Scroll

Display years in a scrollable list (the default when yearGrid is not set).

vue
<template>
  <HLDatePicker id="date-picker-with-year-scroll" type="year" clearable />
</template>
<script setup lang="ts">
import { HLDatePicker } from '@platform-ui/highrise'
</script>

Week Picker

Select a full week at a time. The weekLength prop controls the number of days in the selection (defaults to 7).

vue
<template>
  <HLDatePicker id="date-picker-week" type="week"  />
</template>
<script setup lang="ts">
import { HLDatePicker } from '@platform-ui/highrise'
</script>

Custom Week Length

Set weekLength to change how many days are selected at once. Here :weekLength="5" selects a 5-day span instead of the default 7.

vue
<template>
  <HLDatePicker id="date-picker-week-custom" type="week" :weekLength="5" />
</template>
<script setup lang="ts">
import { HLDatePicker } from '@platform-ui/highrise'
</script>

Inline Mode

Render the date picker inline without a popover. Useful for embedding a date picker directly in a form or layout.

vue
<template>
  <HLDatePicker id="date-picker-inline" type="date" inline />
</template>
<script setup lang="ts">
import { HLDatePicker } from '@platform-ui/highrise'
</script>

Inline Date Range

Start date - End date
vue
<template>
  <HLDatePicker id="date-picker-inline-range" type="daterange" inline  :placeholder="['Start date', 'End date']" />
</template>
<script setup lang="ts">
import { HLDatePicker } from '@platform-ui/highrise'
</script>

Grid View

Use the gridView prop to display month and year selection in a grid layout instead of a scrollable list.

vue
<template>
  <HLDatePicker id="date-picker-grid-view" type="date" clearable gridView />
</template>
<script setup lang="ts">
import { HLDatePicker } from '@platform-ui/highrise'
</script>

With Shortcuts

Add quick selection options:

vue
<template>
  <HLDatePicker id="date-picker-with-shortcuts" type="date" :shortcuts="shortcuts" showCTA />
</template>
<script setup lang="ts">
import { HLDatePicker } from '@platform-ui/highrise'
import { shortcuts } from './options'
</script>
ts
export const shortcuts = {
  Yesterday: () => Date.now() - 24 * 60 * 60 * 1000,
  'My Anniversary': () => 1715020800000,
  'GHL Birthday': () => 1521454800000,
}

Date Range Selection

Select a start and end date with type="daterange".

vue
<template>
  <HLDatePicker type="daterange" clearable :placeholder="['Start date', 'End date']" />
</template>
<script setup lang="ts">
import { HLDatePicker } from '@platform-ui/highrise'
</script>

Single Panel

The default type="daterange" picker shows two calendars side-by-side (start month + end month). On narrow screens that's too wide to fit. Pass singlePanel to switch to a single fluid calendar that fills the width it's given.

singlePanel only applies to type="daterange". It's a no-op for other types.

vue
<template>
  <div style="max-width: 360px;">
    <HLDatePicker type="daterange" singlePanel showCTA />
  </div>
</template>
<script setup lang="ts">
import { HLDatePicker } from '@platform-ui/highrise'
</script>

Example — inline panel mode (calendar always visible)

With panel, the calendar renders directly in the document flow and stretches to its container.

vue
<template>
  <div style="max-width: 400px;">
    <HLDatePicker type="daterange" singlePanel panel showCTA />
  </div>
</template>
<script setup lang="ts">
import { HLDatePicker } from '@platform-ui/highrise'
</script>

Custom Prefix Icon

You might want to use different icon for the prefix.

vue
<template>
  <HLDatePicker type="date">
    <template #prefix>
      <div class="hr-input__prefix-icon">
        <ClockIcon />
      </div>
    </template>
  </HLDatePicker>
</template>
<script setup lang="ts">
import { HLDatePicker } from '@platform-ui/highrise'
import { ClockIcon } from '@gohighlevel/ghl-icons/24/outline'
</script>

Custom Trigger

vue
<template>
  <HLDatePicker type="date" v-model:show="isOpen" @update:value="handleUpdateValue">
    <template #trigger>
      <HLButton size="md" variant="primary" color="blue" @click="isOpen = !isOpen">
        <template #iconLeft>
          <CalendarIcon />
        </template>
        {{ selectedValue ? new Date(selectedValue).toLocaleDateString() : 'Select a date' }}
      </HLButton>
    </template>
  </HLDatePicker>
</template>
<script setup lang="ts">
import { HLDatePicker } from '@platform-ui/highrise'
import { HLButton } from '@platform-ui/highrise'
import { CalendarIcon } from '@gohighlevel/ghl-icons/24/outline'
import { ref } from 'vue'

const isOpen = ref(false)
const selectedValue = ref<number | null>(null)
const handleUpdateValue = (value: number | null) => {
  selectedValue.value = value
}
</script>

Disabled Dates

You can disable specific dates using the isDateDisabled prop. This function receives a timestamp and should return true for dates that should be disabled. In this example, all dates before the 15th of each month are disabled.

vue
<template>
  <HLDatePicker id="date-picker-with-disabled-dates" type="date" :isDateDisabled="disabledDates" />
</template>
<script setup lang="ts">
import { HLDatePicker } from '@platform-ui/highrise'
// Disable all dates before the 15th of each month
const disabledDates = timestamp => {
  const date = new Date(timestamp).getDate()
  return date < 15
}
</script>

Disabled Dates for a Range

For type="daterange", isDateDisabled receives two extra arguments: position ('start' or 'end') and the current [start, end] value. This lets you constrain one endpoint relative to the other — here the end date is limited to within 7 days of the selected start date.

vue
<template>
  <HLDatePicker
    type="daterange"
    :isDateDisabled="rangeDisabled"
    :placeholder="['Start date', 'End date']"
  />
</template>
<script setup lang="ts">
import { HLDatePicker } from '@platform-ui/highrise'

// Limit the end date to within 7 days of the chosen start date
const rangeDisabled = (
  timestamp: number,
  position: 'start' | 'end',
  value: [number, number] | null
) => {
  const week = 7 * 24 * 60 * 60 * 1000
  if (position === 'end' && value && value[0] != null) {
    return Math.abs(timestamp - value[0]) > week
  }
  return false
}
</script>

Limit Selectable Years

Use minYear and maxYear to bound the year range shown in the year and decade views. Years outside the range are not selectable and typed input outside the range is rejected.

vue
<template>
  <HLDatePicker type="date" clearable :minYear="2020" :maxYear="2030" />
</template>
<script setup lang="ts">
import { HLDatePicker } from '@platform-ui/highrise'
</script>

Form Validation

Wrap the picker in an HLFormItem and drive validation from HLForm's :model and :rules. Bind the field with v-model:value and set the item's path to the model key. A common CRM use case is requiring a future date for scheduling.

vue
<template>
  <HLForm :model="formModel" :rules="formRules" label-placement="top">
    <HLFormItem label="Event date" path="eventDate">
      <HLDatePicker type="date" clearable v-model:value="formModel.eventDate" />
    </HLFormItem>
  </HLForm>
</template>
<script setup lang="ts">
import { HLDatePicker, HLForm, HLFormItem } from '@platform-ui/highrise'
import { rules as formRules } from './options'
import { ref } from 'vue'

const formModel = ref({ eventDate: null })
</script>
ts
// Naive expects a sync validator to RETURN an Error to fail (not throw).
export const rules = {
  eventDate: {
    required: true,
    trigger: ['blur', 'change'],
    validator: (_rule, value: number | null) => {
      if (!value) return new Error('Please select a date')
      if (value < Date.now()) return new Error('Date cannot be in the past')
      return true
    },
  },
}

Handling Events

The picker emits value changes through @update:value (raw timestamp) and @update:formatted-value (display string). When showCTA is enabled, @confirm and @cancel fire with both the formatted string and the raw value.

vue
<template>
  <HLDatePicker
    type="date"
    clearable
    showCTA
    @update:value="handleValue"
    @confirm="handleConfirm"
    @cancel="handleCancel"
    @clear="handleClear"
  />
</template>
<script setup lang="ts">
import { HLDatePicker } from '@platform-ui/highrise'

const handleValue = (value: number | [number, number] | null) => {
  console.log('raw timestamp', value)
}
const handleConfirm = (
  formatted: string | [string, string],
  rawValue: number | [number, number] | null
) => {
  console.log('confirmed', formatted, rawValue)
}
const handleCancel = () => console.log('cancelled')
const handleClear = () => console.log('cleared')
</script>

Teleport Target

By default the popover teleports to <body>, which keeps it above other content but detaches it from scrolling containers. Pass to a CSS selector or element to mount the calendar inside a specific container (so it stays anchored on scroll), or to="false" to render it in place.

vue
<template>
  <div id="scroll-container" style="height: 200px; overflow: auto;">
    <HLDatePicker type="date" clearable to="#scroll-container" />
  </div>
</template>
<script setup lang="ts">
import { HLDatePicker } from '@platform-ui/highrise'
</script>

Using Side Content Panels

The DatePicker component supports left and right slots that can be used to add additional content beside the calendar. This is useful for adding quick selections, filters, or any other complementary functionality.

vue
<template>
  <HLDatePicker id="date-picker-with-side-content" type="date" showCTA>
    <template #left>
      <div class="flex flex-col gap-2">
        <h3 class="text-sm font-semibold text-gray-700">Time of Day</h3>
        <div class="flex flex-col gap-1">
          <HLButton
            v-for="time in times"
            :key="time.label"
            size="2xs"
            :variant="time.selected ? 'primary' : 'secondary'"
            @click="time.selected = !time.selected"
          >
            {{ time.label }}
          </HLButton>
        </div>
      </div>
    </template>
    <template #right>
      <div class="flex flex-col gap-2">
        <h3 class="text-sm font-semibold text-gray-700">Category</h3>
        <div class="flex flex-col gap-1">
          <HLButton
            v-for="category in categories"
            :key="category.label"
            size="2xs"
            :variant="category.selected ? 'primary' : 'secondary'"
            @click="category.selected = !category.selected"
          >
            {{ category.label }}
          </HLButton>
        </div>
      </div>
    </template>
  </HLDatePicker>
</template>

<script setup lang="ts">
import { HLDatePicker, HLButton } from '@platform-ui/highrise'
import { times, categories } from './options'
</script>
ts
import { ref } from 'vue'

export const times = ref([
  { label: 'Morning', selected: false },
  { label: 'Afternoon', selected: false },
  { label: 'Evening', selected: false },
])

export const categories = ref([
  { label: 'Meeting', selected: false },
  { label: 'Event', selected: false },
  { label: 'Task', selected: false },
])

Panel Mode

Panel mode renders just the calendar panel without the input and popover wrapper. This is useful when you want to embed the calendar directly in your UI:

vue
<template>
  <HLDatePicker type="date" panel :shortcuts="shortcuts" @update:value="handleDateSelect" />
</template>

<script setup lang="ts">
import { HLDatePicker } from '@platform-ui/highrise'
import { shortcuts } from './options'

const handleDateSelect = (value: number | null) => {
  // Handle date selection
  console.log('Selected date:', value ? new Date(value) : null)
}
</script>
ts
export const shortcuts = {
  Yesterday: () => Date.now() - 24 * 60 * 60 * 1000,
  'My Anniversary': () => 1715020800000,
  'GHL Birthday': () => 1521454800000,
}

The panel mode supports all the same features as the regular datepicker:

  • Single date and date range selection
  • Month and year views
  • Shortcuts
  • Left and right slots for additional content
  • All event handlers

Supported Formats

The following format strings are tested and supported by the format prop. Example outputs are for 15 May 2023.

Format stringExample outputTypical type
dd / MM / yyyy15 / 05 / 2023date (default)
yyyy - MM - dd2023 - 05 - 15date
yyyy/MM/dd2023/05/15date
yy/MM/dd23/05/15date
MMMM dd, yyyyMay 15, 2023date
MMM d, yyyyMay 15, 2023date
MM/yyyy05/2023month
MMMM yyyyMay 2023month
MMM yyyyMay 2023month
yyyy2023year
Y-w2023-20week

Design Guidelines

Input components use a box-shadow to render their focus ring. Box-shadows render outside the element's bounds and may be clipped by any ancestor using overflow: hidden (e.g. Tab Panels or Dropdown Menus).

To prevent this, add a small gutter padding to the component's wrapper to ensure there is enough room for the focus ring to render without being cut off.

vue
<div class="p-[3px]">
  <!-- Your component here -->
</div>

Accessibility

  • Connect the input to its label via aria-labelledby / aria-label, and surface format hints through aria-describedby.
  • Announce value changes via aria-live="polite".

Imports

ts
import { HLDatePicker } from '@platform-ui/highrise'

Props

NameTypeDefaultDescription
type'date' | 'month' | 'year' | 'daterange' | 'week''date'Type of the date picker
clearablebooleanfalseWhether the value can be cleared
disabledbooleanfalseWhether the date picker is disabled
formatstringBased on typeFormat of the date display. See Supported Formats for the tested format strings.
defaultValuenumber | [number, number] | nullnullUncontrolled initial timestamp value in milliseconds (e.g., 1683849600000). Provide an array of two timestamps for daterange. Ignored once value is set.
valuenumber | [number, number] | nullnullControlled value of the date picker (timestamp in milliseconds). Bind with v-model:value or pass value + @update:value. Provide an array of two timestamps for daterange.
placeholderstring | [string, string]'Select Date'Placeholder text. For daterange, provide an array of two strings
shortcuts{ [key: string]: number | (() => number) | [number, number] | (() => [number, number]) } | undefinedundefinedShortcut or quick selection options. Values can be timestamps or functions returning timestamps
showboolean | undefinedundefinedControls panel visibility
showCTAbooleanfalseShow confirm/clear actions
yearGridbooleanfalseShow year selection in grid format
gridViewbooleanfalseShow month/year selection in grid format
size'lg' | 'md' | 'sm' | 'xs' | '2xs' | '3xs''md'Size of the date picker
isDateDisabled(timestamp: number) => boolean for single types
(timestamp: number, position: 'start' | 'end', value: [number, number] | null) => boolean for daterange
undefinedPredicate to disable specific dates; return true to disable. For date / month / year / week it receives just the timestamp (in milliseconds). For daterange it also receives which endpoint is being evaluated ('start' or 'end') and the current [start, end] value, so you can disable dates relative to the other endpoint.
updateValueOnConfirmbooleantrueWhen true (default) and showCTA is true, updates will only be emitted on confirm. When false, updates will be emitted immediately on selection. This prop has no effect when showCTA is false.
panelbooleanfalseWhen true, renders only the calendar panel without input and popover
inlinebooleanfalseWhen true, renders the date picker inline without a popover
placementstring'bottom-start'Placement of the date picker popup relative to the trigger. When unset it resolves to 'bottom-start' (LTR) or 'bottom-end' (RTL).
minYearnumber1900Minimum year to display
maxYearnumber2100Maximum year to display
weekLengthnumber7Number of days in a week selection when type is 'week'
tostring | HTMLElement | falseundefinedTeleport target for the date picker popover. Pass a CSS selector or HTMLElement to mount the calendar inside a specific container. Pass false to disable teleporting.
singlePanelbooleanfalseFor type="daterange", renders a single calendar (instead of two side-by-side) and lets the panel stretch to its container's width. In popover mode, the popover is also sized to match the trigger input's width. Intended for narrow / mobile layouts.

Type Examples

ts
// Single date value (timestamp in milliseconds)
const singleDate: number = Date.now() // e.g., 1683849600000

// Date range value (array of timestamps)
const dateRange: [number, number] = [
  new Date('2023-05-01').getTime(), // start date
  new Date('2023-05-31').getTime(), // end date
]

Emits

NameParametersDescription
@update:value(value: number | [number, number] | null) => voidTriggered when value changes
@update:formatted-value(value: string | [string, string]) => voidTriggered when formatted value changes
@update:show(value: boolean) => voidTriggered when panel visibility changes
@next-month() => voidTriggered when switching to next month
@prev-month() => voidTriggered when switching to prev month
@next-year() => voidTriggered when switching to next year
@prev-year() => voidTriggered when switching to prev year
@confirm(value: string | [string, string], rawValue: number | [number, number] | null) => voidTriggered when confirming the date. value is the formatted display string, rawValue is the timestamp
@cancel(value: string | [string, string], rawValue: number | [number, number] | null) => voidTriggered when canceling the date. value is the formatted display string, rawValue is the timestamp
@clear() => voidTriggered when clearing the date input
@focus() => voidTriggered when focusing the date picker
@blur() => voidTriggered when blurring the date picker
@clickoutside() => voidTriggered when clicking outside the date picker
@final-click(value: number | [number, number] | null) => voidTriggered when a final date selection is made in the panel

Slots

NameDescription
prefixCustom prefix content for the input
suffixCustom suffix content for the input
triggerCustom trigger element for the date picker
leftContent for the left side panel
rightContent for the right side panel

Methods

The DatePicker component exposes the following methods that can be accessed using template refs:

MethodDescription
focus()Focuses the first input element of the date picker. For date range pickers, focuses the start date input.
blur()Removes focus from the first input element. For date range pickers, blurs the start date input.
clear()Clears the selected date value(s), resets formatted values, and emits appropriate events.
closePicker()Programmatically closes the date picker popover.
syncPosition()Recalculates and updates the popover position. Useful after layout changes.

Note: The DatePicker uses v-model:show binding to control panel visibility, so there's no need to manually control the panel visibility through methods.