Time Picker
A component for selecting time with optional timezone and AM/PM support.
Basic Usage
Basic time picker with 24-hour format:
<template>
<HLTimePicker v-model:value="time" format="HH:mm:ss" />
</template>
<script setup lang="ts">
import { HLTimePicker } from '@platform-ui/highrise'
import { ref } from 'vue'
const time = ref(null)
</script>Selection Behavior
When you click a column in the panel, the picker sets that column and resets every column you did not touch to 00. It never fills unselected columns from the current system time.
With Custom Prefix and Suffix
Using both prefix and suffix slots to add custom icons:
<template>
<HLTimePicker v-model:value="time" format="HH:mm:ss">
<template #prefix>
<HLIcon>
<CalendarIcon />
</HLIcon>
</template>
<template #suffix>
<HLIcon>
<InfoCircleIcon />
</HLIcon>
</template>
</HLTimePicker>
</template>
<script setup lang="ts">
import { HLTimePicker, HLIcon } from '@platform-ui/highrise'
import { InfoCircleIcon, CalendarIcon } from '@gohighlevel/ghl-icons/24/outline'
import { ref } from 'vue'
const time = ref(null)
</script>With AM/PM and Timezone
The time picker provides independent am/pm and timezone selectors that emit respective values. Timezones should be passed as an array of objects with the following structure:
interface HLTimezone {
label: string // Display name shown to users
value: string // Timezone identifier (IANA timezone or custom)
default?: boolean // Set to true for the default selected timezone
}
// IANA timezone identifiers
const timezones = [
{ label: 'Eastern Time', value: 'America/New_York', default: true },
{ label: 'Central Time', value: 'America/Chicago' },
{ label: 'Mountain Time', value: 'America/Denver' },
{ label: 'Pacific Time', value: 'America/Los_Angeles' },
{ label: 'UTC', value: 'UTC' },
]WARNING
The time picker does not automatically adjust the displayed time when timezone or AM/PM changes. This gives you full control to implement your own timezone conversion and formatting logic.
<template>
<HLTimePicker
v-model:value="time"
format="hh:mm a"
:showAMPM="true"
:timezones="timezones"
@update:value="handleTimeChange"
@update:ampm="handleAmPmChange"
@update:timezone="handleTimezoneChange"
/>
</template>
<script setup lang="ts">
import { HLTimePicker } from '@platform-ui/highrise'
import { ref } from 'vue'
import { timezones } from './options'
const time = ref(null)
const selectedAmPm = ref('AM')
const selectedTimezone = ref(null)
// Handle independent value changes
const handleTimeChange = timeValue => {
time.value = timeValue
// Apply your timezone conversion logic here
console.log('Time:', timeValue, 'AM/PM:', selectedAmPm.value, 'Timezone:', selectedTimezone.value)
}
const handleAmPmChange = ampm => {
selectedAmPm.value = ampm
// Apply your 12-hour conversion logic here
}
const handleTimezoneChange = timezone => {
selectedTimezone.value = timezone
// Apply your timezone conversion logic here
}
</script>export const timezones = [
{ label: 'UTC', value: 'UTC' },
{ label: 'America/New_York', value: 'America/New_York' },
{ label: 'America/Chicago', value: 'America/Chicago' },
{ label: 'America/Denver', value: 'America/Denver' },
{ label: 'America/Los_Angeles', value: 'America/Los_Angeles' },
]Format
format is a date-fns format string. It does double duty: it decides which columns the panel shows and how the time is displayed and emitted.
Supported tokens
| Token | Meaning | Example output |
|---|---|---|
HH | Hours, 24-hour, zero-padded (00–23) | 18 |
H | Hours, 24-hour, no padding (0–23) | 18 |
hh | Hours, 12-hour, zero-padded (01–12) | 06 |
h | Hours, 12-hour, no padding (1–12) | 6 |
mm | Minutes, zero-padded (00–59) | 01 |
m | Minutes, no padding | 1 |
ss | Seconds, zero-padded (00–59) | 00 |
s | Seconds, no padding | 0 |
a | AM/PM marker | PM |
Which columns appear follows directly from the tokens present:
- an
Horhshows the hours column - an
mshows the minutes column - an
sshows the seconds column - an
h(lowercase) makes the hour column count1–12instead of0–23
Separators are free-form — :, ., or spaces all work, and the same characters come back in the emitted string.
Column combinations
Each picker below is seeded with the same time (18:01:00) so you can compare how the format changes both the input text and the panel columns.
<template>
<!-- Hours, minutes, seconds -->
<HLTimePicker v-model:value="time" format="HH:mm:ss" />
<!-- Hours and minutes only — no seconds column -->
<HLTimePicker v-model:value="time" format="HH:mm" />
<!-- Hours only -->
<HLTimePicker v-model:value="time" format="HH" />
<!-- Minutes and seconds, no hours column (e.g. a duration) -->
<HLTimePicker v-model:value="time" format="mm:ss" />
</template>
<script setup lang="ts">
import { HLTimePicker } from '@platform-ui/highrise'
import { ref } from 'vue'
const time = ref(1183135260000)
</script>12-hour vs 24-hour
A lowercase h switches the hour column to 12-hour counting. Uppercase H keeps it at 24-hour.
<template>
<!-- 24-hour: the hour column runs 00–23 -->
<HLTimePicker v-model:value="time" format="HH:mm" />
<!-- 12-hour: the hour column runs 01–12 -->
<HLTimePicker v-model:value="time" format="hh:mm" />
</template>
<script setup lang="ts">
import { HLTimePicker } from '@platform-ui/highrise'
import { ref } from 'vue'
const time = ref(1183135260000)
</script>How format and showAMPM interact
There are two independent ways to bring AM/PM into the picker, and they produce different UI:
| Setup | AM/PM selector shown? | Where AM/PM appears | Emitted formatted-value |
|---|---|---|---|
format="hh:mm" | No | Nowhere — ambiguous | 06:01 |
format="hh:mm a" | Yes | A separate dropdown beside the input | 06:01 PM |
format="hh:mm" + :showAMPM="true" | Yes | A separate dropdown beside the input | 06:01 |
format="hh:mm a" + :showAMPM="true" | Yes | Dropdown only — the a is stripped from the input text | 06:01 PM |
Two rules explain the table:
- An
ainformatturns the selector on by itself. You do not have to setshowAMPM— the component treats a format containingaas AM/PM mode. - When
showAMPMistrue, theatoken is removed from the text shown in the input, because the dropdown is already displaying it. Theais still honoured in the value emitted by@update:formatted-value, so your data keeps the marker either way.
<template>
<!-- `a` alone: the AM/PM dropdown appears without setting showAMPM -->
<HLTimePicker v-model:value="time" format="hh:mm a" />
<!-- showAMPM alone: dropdown appears, formatted value has no marker -->
<HLTimePicker v-model:value="time" format="hh:mm" :showAMPM="true" />
<!-- Both: dropdown appears, `a` is stripped from the input text but kept in the emitted value -->
<HLTimePicker v-model:value="time" format="hh:mm a" :showAMPM="true" />
</template>
<script setup lang="ts">
import { HLTimePicker } from '@platform-ui/highrise'
import { ref } from 'vue'
const time = ref(1183135260000)
</script>INFO
Changing the AM/PM dropdown rewrites the underlying 24-hour value — picking PM on 06:01 produces 18:01, and the component re-emits @update:value with the new timestamp. It does not merely relabel the display.
WARNING
:showAMPM="true" with a 24-hour format (HH) is contradictory: the hour column still counts 00–23, while the dropdown tries to force the hour into a 12-hour half. Selecting AM on 18:01 rewrites it to 06:01. Use a lowercase h whenever the AM/PM selector is visible.
Seeing the emitted value
format also determines the string emitted by @update:formatted-value, while @update:value always emits a plain millisecond timestamp regardless of format. Pick a time below to see both.
<template>
<HLTimePicker
v-model:value="time"
format="hh:mm:ss a"
:showAMPM="true"
@update:formatted-value="val => (formatted = val)"
/>
<div>
<div>@update:value (ms): {{ time }}</div>
<div>@update:formatted-value: {{ formatted }}</div>
</div>
</template>
<script setup lang="ts">
import { HLTimePicker } from '@platform-ui/highrise'
import { ref } from 'vue'
const time = ref(1183135260000)
const formatted = ref(null)
</script>INFO
The placeholder is derived from format when you don't pass placeholder.time — single tokens are doubled (H → HH) and A is appended in AM/PM mode, so H:m yields the hint HH:mm A.
With Shortcuts
Time picker with predefined time shortcuts:
<template>
<HLTimePicker v-model:value="time" :shortcuts="shortcuts" />
</template>
<script setup lang="ts">
import { HLTimePicker } from '@platform-ui/highrise'
import { ref } from 'vue'
import { shortcuts } from './options'
const time = ref(null)
</script>export const shortcuts = {
Now: () => Date.now(),
'Start of Day': () => {
const date = new Date()
date.setHours(0, 0, 0, 0)
return date.getTime()
},
'End of Day': () => {
const date = new Date()
date.setHours(23, 59, 59, 999)
return date.getTime()
},
}Without CTA (Action Buttons)
Time picker without confirm and clear buttons:
<template>
<HLTimePicker v-model:value="time" :showCTA="false" />
</template>
<script setup lang="ts">
import { HLTimePicker } from '@platform-ui/highrise'
import { ref } from 'vue'
const time = ref(null)
</script>Auto-Close on Selection
Enable auto-close to automatically close the time picker panel after the last required time component is selected. This is useful for quick time selection workflows.
INFO
Auto-close only works when showCTA is false. When CTA buttons are visible, users must explicitly click confirm or clear.
<template>
<!-- Auto-closes after selecting minutes (last required column) -->
<HLTimePicker v-model:value="time" :showCTA="false" :autoClose="true" format="HH:mm" />
</template>
<script setup lang="ts">
import { HLTimePicker } from '@platform-ui/highrise'
import { ref } from 'vue'
const time = ref(null)
</script>With Form Validation
Time picker with error state and validation message:
Please enter a valid time
<template>
<HLFormItem label="Time" validation-status="error" feedback="Please enter a valid time">
<HLTimePicker v-model:value="time" status="error">
<template #suffix>
<HLIcon color="var(--error-600)">
<InfoCircleIcon />
</HLIcon>
</template>
</HLTimePicker>
</HLFormItem>
</template>
<script setup lang="ts">
import { HLTimePicker, HLFormItem, HLIcon } from '@platform-ui/highrise'
import { InfoCircleIcon } from '@gohighlevel/ghl-icons/24/outline'
import { ref } from 'vue'
const time = ref(null)
</script>With Custom Placeholders
Time picker with custom placeholder text for time, timezone, and AM/PM selectors:
<template>
<HLTimePicker
v-model:value="time"
:showAMPM="true"
:timezones="timezones"
:placeholder="{
time: 'Enter time',
timezone: 'Choose your timezone',
}"
/>
</template>
<script setup lang="ts">
import { HLTimePicker } from '@platform-ui/highrise'
import { ref } from 'vue'
import { timezones } from './options'
const time = ref(null)
</script>export const timezones = [
{ label: 'UTC', value: 'UTC' },
{ label: 'America/New_York', value: 'America/New_York' },
{ label: 'America/Chicago', value: 'America/Chicago' },
{ label: 'America/Denver', value: 'America/Denver' },
{ label: 'America/Los_Angeles', value: 'America/Los_Angeles' },
]Disabling Time
You can restrict time selection by disabling certain hours or minutes or seconds.
When a disabled time is selected, the component will automatically fall back to the first available enabled time and emit an update:fallback event describing the requested and applied values.
<template>
<HLTimePicker
v-model:value="time"
format="HH:mm:ss"
:is-hour-disabled="(hour) => hour < 9 || hour > 17"
:placeholder="{ time: 'Business hours only (9 AM - 5 PM)' }"
/>
</template>
<script setup lang="ts">
import { HLTimePicker } from '@platform-ui/highrise'
import { ref } from 'vue'
const time = ref(null)
</script>Disable Minutes and Seconds
isMinuteDisabled and isSecondDisabled restrict minutes and seconds. Both receive the current higher-order selection, so you can make the rules depend on the chosen hour (and minute). Here minutes are limited to quarter-hour marks and seconds to 0.
<template>
<HLTimePicker
v-model:value="time"
format="HH:mm:ss"
:is-minute-disabled="(minute) => minute % 15 !== 0"
:is-second-disabled="(second) => second !== 0"
:placeholder="{ time: 'Quarter-hour marks only' }"
/>
</template>
<script setup lang="ts">
import { HLTimePicker } from '@platform-ui/highrise'
import { ref } from 'vue'
const time = ref(null)
</script>Sizes
Set size to scale the input; it accepts lg, md, sm, xs, 2xs, and 3xs.
<template>
<HLTimePicker v-model:value="time" size="lg" format="HH:mm" />
<HLTimePicker v-model:value="time" size="md" format="HH:mm" />
<HLTimePicker v-model:value="time" size="sm" format="HH:mm" />
<HLTimePicker v-model:value="time" size="xs" format="HH:mm" />
<HLTimePicker v-model:value="time" size="2xs" format="HH:mm" />
<HLTimePicker v-model:value="time" size="3xs" format="HH:mm" />
</template>
<script setup lang="ts">
import { HLTimePicker } from '@platform-ui/highrise'
import { ref } from 'vue'
const time = ref(null)
</script>Disabled
Pass disabled as a boolean to disable the whole component, or as an object ({ time, timezone, ampm }) to disable only specific parts.
<template>
<!-- Fully disabled -->
<HLTimePicker v-model:value="time" format="hh:mm a" :showAMPM="true" :timezones="timezones" :disabled="true" />
<!-- Only the timezone and AM/PM selectors disabled; time input stays editable -->
<HLTimePicker v-model:value="time" format="hh:mm a" :showAMPM="true" :timezones="timezones" :disabled="{ timezone: true, ampm: true }" />
</template>
<script setup lang="ts">
import { HLTimePicker } from '@platform-ui/highrise'
import { ref } from 'vue'
import { timezones } from './options'
const time = ref(1183135260000)
</script>Default Value
defaultValue (milliseconds) sets the time the picker resets to when cleared, rather than emptying entirely.
<template>
<HLTimePicker v-model:value="time" :defaultValue="defaultTime" format="HH:mm:ss" />
</template>
<script setup lang="ts">
import { HLTimePicker } from '@platform-ui/highrise'
import { ref } from 'vue'
const time = ref(null)
// 2007-06-29 18:01:00 in ms
const defaultTime = 1183135260000
</script>Custom Widths
Use timeInputWidth and ampmSelectWidth to override the auto-sized widths of the time input and the AM/PM selector.
<template>
<HLTimePicker
v-model:value="time"
format="hh:mm a"
:showAMPM="true"
:timeInputWidth="200"
:ampmSelectWidth="100"
/>
</template>
<script setup lang="ts">
import { HLTimePicker } from '@platform-ui/highrise'
import { ref } from 'vue'
const time = ref(null)
</script>Teleport Target
Use to to control where the popover panel mounts. Pass a CSS selector or element to teleport it into a specific container, or false to render it inline (useful inside scrolling or overflow-hidden containers).
<template>
<!-- Render the panel inline instead of teleporting to <body> -->
<HLTimePicker v-model:value="time" format="HH:mm:ss" :to="false" />
</template>
<script setup lang="ts">
import { HLTimePicker } from '@platform-ui/highrise'
import { ref } from 'vue'
const time = ref(null)
</script>Event Testing
This example logs the events the time picker emits as you interact with it. Try the following:
- Pick a time to test
@update:valueand@update:formatted-value - Click confirm to test
@update:confirm, or clear to test@update:clear - Change AM/PM or timezone to test
@update:ampmand@update:timezone - Select a disabled hour to trigger
@update:fallback
Event Log:
<template>
<HLTimePicker
v-model:value="time"
format="hh:mm:ss a"
:showAMPM="true"
:timezones="timezones"
:is-hour-disabled="(hour) => hour < 9 || hour > 17"
@update:value="val => addEventLog('@update:value → ' + val)"
@update:formatted-value="val => addEventLog('@update:formatted-value → ' + val)"
@update:confirm="val => addEventLog('@update:confirm → ' + val)"
@update:clear="addEventLog('@update:clear')"
@update:ampm="val => addEventLog('@update:ampm → ' + val)"
@update:timezone="tz => addEventLog('@update:timezone → ' + (tz ? tz.value : 'null'))"
@update:fallback="info => addEventLog('@update:fallback → ' + info.reason)"
/>
<div class="text-sm">
<p class="font-bold mb-2">Event Log:</p>
<div v-if="eventLog.length === 0" class="text-gray-500">No events logged yet. Interact with the picker above.</div>
<div v-for="(log, index) in eventLog" :key="index" class="text-gray-700">{{ log.timestamp }}: {{ log.event }}</div>
</div>
</template>
<script setup lang="ts">
import { ref } from 'vue'
import { HLTimePicker } from '@platform-ui/highrise'
import { timezones } from './options'
const time = ref(null)
const eventLog = ref<{ event: string; timestamp: string }[]>([])
const addEventLog = (event: string) => {
eventLog.value.unshift({ event, timestamp: new Date().toLocaleTimeString() })
if (eventLog.value.length > 5) {
eventLog.value.pop()
}
}
</script>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
- Tie the input to its label via
aria-labelledby/aria-labeland surface format hints witharia-describedby. - Toggle
aria-expanded/aria-controlson the trigger when the time panel opens, and flag the active option usingaria-selected. - Announce programmatic time changes inside an
aria-live="polite"region when the value updates automatically.
Props
| Prop | Type | Default | Description |
|---|---|---|---|
| size | 'lg' | 'md' | 'sm' | 'xs' | '2xs' | '3xs' | 'md' | Size of the time picker input |
| format | string | 'HH:mm:ss' | Time format string (follows date-fns format) |
| showAMPM | boolean | false | Whether to show AM/PM selector |
| disabled | boolean | TimePickerDisabledState | false | Disable the entire component or specific parts |
| placeholder | TimePickerPlaceholders | { time: 'Select time', timezone: 'Select timezone' } | Placeholder text for inputs |
| timezones | HLTimezone[] | [] | Array of timezone options |
| ampmSelectWidth | string | number | Determined based on the input size | Custom width for AM/PM selector |
| timeInputWidth | string | number | Determined based on the input size | Custom width for time input |
| showCTA | boolean | true | Whether to show the confirm and clear buttons |
| autoClose | boolean | false | Auto-close panel after last time component selection. Only works when showCTA is false |
| shortcuts | Record<string, number | (() => number)> | {} | Predefined shortcuts for quick time selection |
| status | 'success' | 'error' | 'warning' | undefined | undefined | Validation status of the input |
| defaultValue | number | undefined | undefined | Default time value when input is cleared in milliseconds |
| value | number | null | null | Value set to the time picker in milliseconds |
| isHourDisabled | (hour: number) => boolean | undefined | Function to determine if a specific hour should be disabled |
| isMinuteDisabled | (minute: number, selectedHour?: number) => boolean | undefined | Function to determine if a specific minute should be disabled, optionally based on selected hour |
| isSecondDisabled | (second: number, selectedHour?: number, selectedMinute?: number) => boolean | undefined | Function to determine if a specific second should be disabled, based on selected hour and minute |
| to | string | HTMLElement | false | undefined | Teleport target for the time picker popover. Pass a CSS selector or HTMLElement to mount the panel inside a specific container. Pass false to disable teleporting. |
Interfaces
TimePickerDisabledState Interface
interface TimePickerDisabledState {
time?: boolean // Disable time input
timezone?: boolean // Disable timezone selector
ampm?: boolean // Disable AM/PM selector
}TimePickerPlaceholders Interface
interface TimePickerPlaceholders {
time?: string // Placeholder for time input
timezone?: string // Placeholder for timezone selector
}HLTimezone Interface (for timezones)
interface HLTimezone {
label: string // Display label for the timezone
value: string // Timezone value (e.g., 'America/Los_Angeles')
default?: boolean // Whether this timezone is the default selection
}Slots
| Name | Parameters | Description |
|---|---|---|
| prefix | - | Content to be placed before the time input |
| suffix | - | Content to be placed after the time input |
Emits
| Event | Arguments | Description |
|---|---|---|
| update:value | (value: number | null) => void | Emitted when time value changes |
| update:formatted-value | (value: string | null) => void | Emitted when formatted time string changes |
| update:clear | () => void | Emitted when time is cleared |
| update:confirm | (value: number | null) => void | Emitted when time is confirmed with selected value |
| update:ampm | (value: 'AM' | 'PM') => void | Emitted when AM/PM selection changes |
| update:timezone | (timezone: HLTimezone | null) => void | Emitted when timezone selection changes |
| update:fallback | (fallbackInfo: HLTimePickerFallbackInfo) => void | Emitted when the timepicker falls back to a valid time |
Methods
| Method | Arguments | Description |
|---|---|---|
| focus | () => void | Focus the time picker input |
| blur | () => void | Remove focus from the time picker input |