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:
<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
<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.
<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.
<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.
<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
<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.
<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".
<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.
<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).
<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).
<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.
<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.
<template>
<HLDatePicker id="date-picker-inline" type="date" inline />
</template>
<script setup lang="ts">
import { HLDatePicker } from '@platform-ui/highrise'
</script>Inline Date Range
<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.
<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:
<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>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".
<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.
<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.
<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.
<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
<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.
<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.
<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.
<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.
<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>// 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.
<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.
<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.
<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>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:
<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>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 string | Example output | Typical type |
|---|---|---|
dd / MM / yyyy | 15 / 05 / 2023 | date (default) |
yyyy - MM - dd | 2023 - 05 - 15 | date |
yyyy/MM/dd | 2023/05/15 | date |
yy/MM/dd | 23/05/15 | date |
MMMM dd, yyyy | May 15, 2023 | date |
MMM d, yyyy | May 15, 2023 | date |
MM/yyyy | 05/2023 | month |
MMMM yyyy | May 2023 | month |
MMM yyyy | May 2023 | month |
yyyy | 2023 | year |
Y-w | 2023-20 | week |
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.
<div class="p-[3px]">
<!-- Your component here -->
</div>Accessibility
- Connect the input to its label via
aria-labelledby/aria-label, and surface format hints througharia-describedby. - Announce value changes via
aria-live="polite".
Imports
import { HLDatePicker } from '@platform-ui/highrise'Props
| Name | Type | Default | Description |
|---|---|---|---|
| type | 'date' | 'month' | 'year' | 'daterange' | 'week' | 'date' | Type of the date picker |
| clearable | boolean | false | Whether the value can be cleared |
| disabled | boolean | false | Whether the date picker is disabled |
| format | string | Based on type | Format of the date display. See Supported Formats for the tested format strings. |
| defaultValue | number | [number, number] | null | null | Uncontrolled initial timestamp value in milliseconds (e.g., 1683849600000). Provide an array of two timestamps for daterange. Ignored once value is set. |
| value | number | [number, number] | null | null | Controlled 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. |
| placeholder | string | [string, string] | 'Select Date' | Placeholder text. For daterange, provide an array of two strings |
| shortcuts | { [key: string]: number | (() => number) | [number, number] | (() => [number, number]) } | undefined | undefined | Shortcut or quick selection options. Values can be timestamps or functions returning timestamps |
| show | boolean | undefined | undefined | Controls panel visibility |
| showCTA | boolean | false | Show confirm/clear actions |
| yearGrid | boolean | false | Show year selection in grid format |
| gridView | boolean | false | Show 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 | undefined | Predicate 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. |
| updateValueOnConfirm | boolean | true | When 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. |
| panel | boolean | false | When true, renders only the calendar panel without input and popover |
| inline | boolean | false | When true, renders the date picker inline without a popover |
| placement | string | 'bottom-start' | Placement of the date picker popup relative to the trigger. When unset it resolves to 'bottom-start' (LTR) or 'bottom-end' (RTL). |
| minYear | number | 1900 | Minimum year to display |
| maxYear | number | 2100 | Maximum year to display |
| weekLength | number | 7 | Number of days in a week selection when type is 'week' |
| to | string | HTMLElement | false | undefined | Teleport 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. |
| singlePanel | boolean | false | For 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
// 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
| Name | Parameters | Description |
|---|---|---|
@update:value | (value: number | [number, number] | null) => void | Triggered when value changes |
@update:formatted-value | (value: string | [string, string]) => void | Triggered when formatted value changes |
@update:show | (value: boolean) => void | Triggered when panel visibility changes |
@next-month | () => void | Triggered when switching to next month |
@prev-month | () => void | Triggered when switching to prev month |
@next-year | () => void | Triggered when switching to next year |
@prev-year | () => void | Triggered when switching to prev year |
@confirm | (value: string | [string, string], rawValue: number | [number, number] | null) => void | Triggered when confirming the date. value is the formatted display string, rawValue is the timestamp |
@cancel | (value: string | [string, string], rawValue: number | [number, number] | null) => void | Triggered when canceling the date. value is the formatted display string, rawValue is the timestamp |
@clear | () => void | Triggered when clearing the date input |
@focus | () => void | Triggered when focusing the date picker |
@blur | () => void | Triggered when blurring the date picker |
@clickoutside | () => void | Triggered when clicking outside the date picker |
@final-click | (value: number | [number, number] | null) => void | Triggered when a final date selection is made in the panel |
Slots
| Name | Description |
|---|---|
prefix | Custom prefix content for the input |
suffix | Custom suffix content for the input |
trigger | Custom trigger element for the date picker |
left | Content for the left side panel |
right | Content for the right side panel |
Methods
The DatePicker component exposes the following methods that can be accessed using template refs:
| Method | Description |
|---|---|
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.