TimePicker

Lets users select a specific time from a list or enter a custom value. Supports 12/24-hour formats, custom intervals, and optional time ranges.

Basic

Meeting time
Selected: 14:30
vue
<script setup lang="ts">
import { ref } from 'vue'
import { TimePicker } from 'frappe-ui'

// "When should we sync?" — typical meeting scheduler time slot
const meetingTime = ref('14:30')
</script>

<template>
  <div class="flex w-72 flex-col gap-1.5">
    <span class="text-sm text-ink-gray-7">Meeting time</span>
    <TimePicker
      v-model="meetingTime"
      placeholder="Pick a time"
      side="bottom"
      align="start"
    >
      <template #prefix>
        <span class="lucide-clock size-4 text-ink-gray-5" />
      </template>
    </TimePicker>
    <span class="text-xs text-ink-gray-5">
      Selected: {{ meetingTime || '—' }}
    </span>
  </div>
</template>

24 Hour Format

Nightly backup runs at
UTC, every day at 02:00
vue
<script setup lang="ts">
import { ref } from 'vue'
import { TimePicker } from 'frappe-ui'

// Schedule a nightly cron — 24h format keeps the ops team unambiguous
const cronTime = ref('02:00')
</script>

<template>
  <div class="flex w-72 flex-col gap-1.5">
    <span class="text-sm text-ink-gray-7">Nightly backup runs at</span>
    <TimePicker
      v-model="cronTime"
      :use12Hour="false"
      :interval="30"
    >
      <template #prefix>
        <span class="lucide-server-cog size-4 text-ink-gray-5" />
      </template>
    </TimePicker>
    <span class="text-xs text-ink-gray-5">UTC, every day at {{ cronTime }}</span>
  </div>
</template>

Custom Options

Book a class
vue
<script setup lang="ts">
import { ref } from 'vue'
import { TimePicker } from 'frappe-ui'

// A yoga studio offers only its scheduled class times — not free-form intervals
const classTime = ref('07:00')

const classOptions = [
  { value: '06:00', label: '6:00 AM — Sunrise Flow' },
  { value: '07:00', label: '7:00 AM — Vinyasa' },
  { value: '09:30', label: '9:30 AM — Gentle Hatha' },
  { value: '12:15', label: '12:15 PM — Lunch Power' },
  { value: '17:30', label: '5:30 PM — Restorative' },
  { value: '19:00', label: '7:00 PM — Candlelight Yin' },
]
</script>

<template>
  <div class="flex w-80 flex-col gap-1.5">
    <span class="text-sm text-ink-gray-7">Book a class</span>
    <TimePicker v-model="classTime" :options="classOptions">
      <template #prefix>
        <span class="lucide-dumbbell size-4 text-ink-gray-5" />
      </template>
    </TimePicker>
  </div>
</template>

Min / Max Range

Shift hours
From
To
Working 09:00 – 17:00
vue
<script setup lang="ts">
import { ref } from 'vue'
import { TimePicker } from 'frappe-ui'

// Shift scheduling — limit each picker to business hours.
const shiftStart = ref('09:00')
const shiftEnd = ref('17:00')
</script>

<template>
  <div class="flex w-full max-w-md flex-col gap-3">
    <span class="text-sm font-medium text-ink-gray-7">Shift hours</span>
    <div class="grid grid-cols-2 gap-3">
      <div class="flex flex-col gap-1.5">
        <span class="text-xs text-ink-gray-6">From</span>
        <TimePicker
          v-model="shiftStart"
          min="06:00"
          :max="shiftEnd"
          :interval="15"
        >
          <template #prefix>
            <span class="lucide-sunrise size-4 text-ink-gray-5" />
          </template>
        </TimePicker>
      </div>
      <div class="flex flex-col gap-1.5">
        <span class="text-xs text-ink-gray-6">To</span>
        <TimePicker
          v-model="shiftEnd"
          :min="shiftStart"
          max="22:00"
          :interval="15"
        >
          <template #prefix>
            <span class="lucide-sunset size-4 text-ink-gray-5" />
          </template>
        </TimePicker>
      </div>
    </div>
    <span class="text-xs text-ink-gray-5">
      Working {{ shiftStart }} – {{ shiftEnd }}
    </span>
  </div>
</template>

API Reference

Show types
typescript
/**
 * TimePicker is a thin wrapper around Combobox that adds time-aware parsing,
 * generated options, and a 12/24-hour display toggle. Canonical value is
 * always 24-hour `HH:mm` (or `HH:mm:ss` if seconds were typed).
 */

export type PopoverSide = 'top' | 'right' | 'bottom' | 'left'
export type PopoverAlign = 'start' | 'center' | 'end'

export type Placement =
  | 'bottom-start'
  | 'top-start'
  | 'top-end'
  | 'bottom-end'
  | 'right-start'
  | 'right-end'
  | 'left-start'
  | 'left-end'

export type Variant = 'outline' | 'subtle'

export interface TimePickerProps {
  /** Controlled value, canonical `HH:mm` (or `HH:mm:ss`). */
  modelValue?: string

  /**
   * Uncontrolled initial value.
   * @deprecated Use `modelValue` with `v-model` instead.
   */
  value?: string

  /** Minute interval between generated options. */
  interval?: number

  /** Caller-provided option values; bypasses the generated grid. */
  options?: Array<{ value: string; label?: string }>

  /** Preferred popover side. */
  side?: PopoverSide

  /** Alignment of the popover along the trigger edge. */
  align?: PopoverAlign

  /** Gap between trigger and popover in pixels. */
  offset?: number

  /**
   * Combined side+align placement.
   * @deprecated Use `side` and `align` instead.
   */
  placement?: Placement

  /** Placeholder text when no value is selected. */
  placeholder?: string

  /** Visual style variant. */
  variant?: Variant

  /**
   * Whether the trigger input accepts typed input. When `false` the user can
   * still open the popover and pick a time, but cannot type a time manually.
   * Default: `true`.
   */
  typeable?: boolean

  /**
   * Prevents manual typing while keeping the picker interactive.
   * @deprecated Use `typeable: false` instead.
   */
  readonly?: boolean

  /**
   * Allows users to type custom time values.
   * @deprecated Use `typeable: false` instead.
   */
  allowCustom?: boolean

  /** Keeps the popover open after a time is selected. */
  keepOpen?: boolean

  /**
   * Closes the popover after a value is picked.
   * @deprecated Use `keepOpen` (inverse semantics).
   */
  autoClose?: boolean

  /** Use 12-hour (am/pm) format for display. */
  use12Hour?: boolean

  /** Disable the time picker. */
  disabled?: boolean

  /** Controlled popover open state. Use with `v-model:open` for two-way binding. */
  open?: boolean

  /** Opens the popover when the input receives focus. Default: false. */
  openOnFocus?: boolean

  /** Opens the popover when the input is clicked. Default: true. */
  openOnClick?: boolean

  /** Minimum selectable time as `HH:mm[:ss]`. */
  min?: string

  /** Maximum selectable time as `HH:mm[:ss]`. */
  max?: string

  /**
   * Minimum selectable time as `HH:mm[:ss]`.
   * @deprecated Use `min` instead.
   */
  minTime?: string

  /**
   * Maximum selectable time as `HH:mm[:ss]`.
   * @deprecated Use `max` instead.
   */
  maxTime?: string

  /**
   * Scroll behavior when opening the list.
   * @deprecated Scrolling is always centered now.
   */
  scrollMode?: 'center' | 'start' | 'nearest'
}

export type TimePickerEmits = {
  (e: 'update:modelValue', value: string): void
  (e: 'update:open', value: boolean): void
  (e: 'change', value: string): void
  (e: 'input-invalid', input: string): void
  (e: 'invalid-change', invalid: boolean): void
  (e: 'open'): void
  (e: 'close'): void
}
modelValue
= ""
string

Controlled value, canonical `HH:mm` (or `HH:mm:ss`).

value

Deprecated — Use `modelValue` with `v-model` instead.

interval
= 15
number

Minute interval between generated options.

options
= []
{ value: string; label?: string; }[] | undefined

Caller-provided option values; bypasses the generated grid.

side
PopoverSide

Preferred popover side.

align
PopoverAlign

Alignment of the popover along the trigger edge.

offset
number

Gap between trigger and popover in pixels.

placement

Deprecated — Use `side` and `align` instead.

placeholder
= "Select time"
string

Placeholder text when no value is selected.

variant
= "subtle" as Variant
Variant

Visual style variant.

typeable
= true
boolean

Whether the trigger input accepts typed input. When `false` the user can still open the popover and pick a time, but cannot type a time manually. Default: `true`.

readonly
= false

Deprecated — Use `typeable: false` instead.

allowCustom
= true

Deprecated — Use `typeable: false` instead.

keepOpen
boolean

Keeps the popover open after a time is selected.

autoClose
= true

Deprecated — Use `keepOpen` (inverse semantics).

use12Hour
= true
boolean

Use 12-hour (am/pm) format for display.

disabled
= false
boolean

Disable the time picker.

open
boolean

Controlled popover open state. Use with `v-model:open` for two-way binding.

openOnFocus
= false
boolean

Opens the popover when the input receives focus. Default: false.

openOnClick
= true
boolean

Opens the popover when the input is clicked. Default: true.

min
string

Minimum selectable time as `HH:mm[:ss]`.

max
string

Maximum selectable time as `HH:mm[:ss]`.

minTime

Deprecated — Use `min` instead.

maxTime

Deprecated — Use `max` instead.

scrollMode

Deprecated — Scrolling is always centered now.

prefix

Rendered inside the trigger input, before the typed value.

suffix
{ togglePopover: () => void; isOpen: boolean; }

Rendered inside the trigger input, after the typed value. Defaults to a chevron-down that toggles the popover.

update:modelValue
[value: string]

Fired when the model value changes.

change
[value: string]

Fired after the value is committed.

open
[]

Fired when the component opens.

update:open
[value: boolean]

Fired when the open state changes.

close
[]

Fired when the component closes.

input-invalid
[input: string]
invalid-change
[invalid: boolean]