Time Picker
Accessible, unstyled listbox of time slots generated from min, max, and step.
| Package | @vizejs/ui/time-picker |
| Maturity | stable |
| Own the source | vize lib pull time-picker |
| Requires | controllable-state, date-field, listbox, locale, time-field |
| Aliases | time slots, appointment slots, time list, time select |
| Covers | WAI-ARIA listbox, HTML datalist step, MUI TimeClock lists |
Minimal setup
Use a Vue 3.5+ project with Vue SFC compilation configured (see Vite+ integration).
vp install @vizejs/ui
Add the public import below to <script setup lang="ts">. Components provide behavior without styles; the preview uses optional Paper styles.
import { TimePicker } from "@vizejs/ui/time-picker";
Or copy the source into your project with vize lib pull time-picker (see Source Distribution).
Try it
This is the basic example maintained with the component source, using the public package imports. The preview adds the optional Paper styles.
Open the interactive example · Styling these components
Copy the example
<!-- Listbox of half-hour appointment slots with an unavailable lunch break. -->
<script setup lang="ts">
import { ref } from "vue";
import { TimePicker } from "@vizejs/ui/time-picker";
import type { PlainTime } from "@vizejs/ui/time-picker";
const slot = ref<PlainTime | null>({ hour: 10, minute: 0, second: 0 });
function label(time: PlainTime): string {
return `${String(time.hour).padStart(2, "0")}:${String(time.minute).padStart(2, "0")}`;
}
function isLunch(time: PlainTime): boolean {
return time.hour === 12;
}
</script>
<template>
<div>
<TimePicker
v-model="slot"
name="appointment"
locale="en-US"
aria-label="Appointment time"
:min="{ hour: 9, minute: 0, second: 0 }"
:max="{ hour: 17, minute: 0, second: 0 }"
:step="30"
:is-time-unavailable="isLunch"
/>
<output>{{ slot ? label(slot) : "No slot" }}</output>
</div>
</template>
API
TimePicker
Source: src/families/date-time/time-picker/time-picker.vue
Props
| Prop | Type | Default | Description |
|---|---|---|---|
id |
string | null | undefined |
undefined |
Consumer-owned base id; the listbox uses <id>-listbox. Nullish values use a deterministic fallback. |
modelValue |
PlainTime | null | undefined |
undefined |
Controlled time; undefined selects uncontrolled mode and null clears. |
defaultValue |
PlainTime | null | undefined |
null |
Initial uncontrolled time. |
min |
PlainTime | null | undefined |
{ hour: 0, minute: 0, second: 0 } |
First slot, inclusive. |
max |
PlainTime | null | undefined |
{ hour: 23, minute: 59, second: 59 } |
Last possible slot, inclusive. |
step |
number | undefined |
30 |
Minutes between slots. |
hourCycle |
HourCycle | undefined |
undefined |
12 or 24-hour labels; defaults to the locale clock. |
isTimeUnavailable |
((time: PlainTime) => boolean) | undefined |
undefined |
Predicate for slots rendered as disabled options. |
locale |
string | undefined |
undefined |
BCP 47 locale for labels and typeahead; defaults to the nearest LocaleProvider. |
disabled |
boolean | undefined |
false |
Disable the listbox. |
readOnly |
boolean | undefined |
false |
Keep navigation while ignoring selection requests. |
required |
boolean | undefined |
false |
Mark the listbox as required. |
name |
string | undefined |
undefined |
Hidden input name that submits HH:MM. |
ariaLabel |
string | undefined |
undefined |
Accessible name for the listbox. |
ariaLabelledby |
string | undefined |
undefined |
Ids that label the listbox. |
ariaDescribedby |
string | undefined |
undefined |
Ids that describe the listbox. |
Events
| Event | Payload | Description |
|---|---|---|
update:modelValue |
[value: PlainTime | null] |
Fired when the picker requests a new controlled time. |
change |
[value: PlainTime | null, previous: PlainTime | null, nativeEvent: Event | null] |
Fired after a distinct selection with the previous time and triggering event. |
Slots
| Slot | Slot props | Description |
|---|---|---|
option |
TimePickerSlot |
Option content. Receives the slot; defaults to its localized label. |
Exposed
| Member | Type | Description |
|---|---|---|
value |
PlainTime | null |
Selected time, when it matches a slot. |
slots |
readonly TimePickerSlot[] |
Generated slots between min and max, step minutes apart. |
hourCycle |
HourCycle |
Resolved hour clock used for labels. |
disabled |
boolean |
Whether the listbox is disabled. |
readOnly |
boolean |
Whether selection is locked. |
focus |
(options?: FocusOptions) => void |
Focus the listbox. |
setValue |
(value: PlainTime | null) => boolean |
Request a value; returns whether it differs. |
Behavior
Normative state x input -> outcome table for time-picker.vue
(@vizejs/ui/time-picker): a role="listbox" of time slots composed from the
Listbox family (listbox.behavior.md covers keyboard, typeahead, and
active-descendant focus). Every row is proven by the named test.
| # | State | Input | Outcome | Proven by |
|---|---|---|---|---|
| TP1 | any | render | options run from min to max every step minutes with localized labels (also the typeahead text); the selected slot is aria-selected; name submits HH:MM |
renders a listbox of localized slots between min and max every step minutes |
| TP2 | enabled | option activation | emits update:modelValue and change with PlainTime; unavailable slots are disabled options; read-only ignores selection |
selecting slots emits PlainTime values and honors unavailable slots and read-only |
| TP3 | imperative | expose / generation | value, slots, hourCycle, focus, setValue; createTimeSlots clamps step to 1–1440 and rounds min up to whole minutes |
exposes value, slots, focus, and setValue; slot generation clamps inputs |
| TP4 | SSR | isolated requests | byte-identical markup and silent hydration | renders byte-identical time picker markup across isolated SSR requests, hydrates time picker markup without mismatches |