Time Fields
Time fields are a type of input field that allows users to enter a time, usually in the format of hh:mm
.
Features
- Labeling, Descriptions, and error messages are automatically linked to the time field elements.
- Validation support with native HTML constraints or Standard Schemas.
- Support for
v-model
binding. - Comprehensive keyboard shortcuts support.
- Focus management and auto navigation for time segments.
Keyboard Features
Key | Description |
---|---|
↓ArrowDown | Decrements selected segment by 1. |
↑ArrowUp | Increments selected segment by 1. |
←ArrowLeft | Moves the focus to the previous segment. |
→ArrowRight | Moves the focus to the next segment. |
⌦Delete | Clears the current segment. |
⌫Backspace | Clears the current segment. |
⇥Tab | Moves the focus to the next segment or next element in the tab index order if it is the last segment. |
Anatomy
Building a Time Field Component
Just like the native HTML time field, the time field in Formwerk is comprised of time segments, each segment represents a part of the time value (e.g: hour, minute).
The segments are generated for you automatically based on the formatOptions
you pass to the useTimeField
composable. You will need to bind the prop objects returned by the composable to the corresponding DOM elements in the anatomy.
If you do not pass the formatOptions
prop, the time field will use an HH:mm
format.
<script setup lang="ts">import { useTimeField, type TimeFieldProps, DateTimeSegment,} from '@formwerk/core';
const props = defineProps<TimeFieldProps>();
const { controlProps, isTouched, labelProps, errorMessageProps, errorMessage, segments, direction,} = useTimeField(props);</script>
<template> <div class="InputTime" :class="{ touched: isTouched }" :dir="direction"> <span class="label">{{ label }}</span>
<div class="control"> <div v-bind="controlProps"> <DateTimeSegment v-for="segment in segments" v-bind="segment" class="segment" /> </div> </div>
<span class="error-message"> {{ errorMessage }} </span> </div></template>
<style scoped>.InputTime { position: relative; width: 100%; margin-bottom: 1.5em;
--color-primary: #10b981; --color-text-primary: #333; --color-text-secondary: #666; --color-border: #d4d4d8; --color-focus: var(--color-primary); --color-error: #f00; --color-background: #fff;}
.label { display: block; margin-bottom: 0.25rem; font-size: 14px; font-weight: 500; color: var(--color-text-primary);}
.control { display: flex; align-items: center; gap: 0.25em; width: max-content; padding: 0.5rem 0.6rem; border: 1px solid var(--color-border); border-radius: 6px; background-color: var(--color-background); color: var(--color-text-primary); font-size: 13px; transition: all 0.3s ease;}
.control:focus-within { border-color: var(--color-focus); box-shadow: 0 0 0 1px var(--color-focus); outline: none;}
.segment { padding: 0.125rem; border-radius: 0.25rem; caret-color: transparent; min-width: 2ch;
&:focus { background-color: var(--color-focus); color: #fff; outline: none; }
&[aria-disabled='true'] { opacity: 0.4; }}
.error-message { display: none; position: absolute; left: 0; margin-top: 0.25rem; font-size: 13px; color: var(--color-error);}
.InputTime:has(:user-invalid),.InputTime:has(.error-message:not(:empty)) { --color-border: var(--color-error); --color-focus: var(--color-error);}
.InputTime:has(:user-invalid) .error-message,.InputTime:has(.error-message:not(:empty)) .error-message { display: block;}
.InputTime:has(:disabled) .control { cursor: not-allowed; opacity: 0.7;}</style>
Validation
HTML Constraints
You can use the following properties to validate the time field with native HTML constraint validation:
Name | Type | Description |
---|---|---|
min | Date | The minimum time that can be entered. |
max | Date | The maximum time that can be entered. |
required | boolean | Whether the time field is required. |
<script setup lang="ts">import TimeField from './TimeField.vue';</script>
<template> <TimeField label="HTML Constraints" required min="00:00" max="10:04" /></template>
Standard Schema
useTimeField
supports Standard Schema validation through the schema
prop. This includes multiple providers like Zod, Valibot, Arktype, and more.
In this example, we are validating that the time field is between 00:00 and 10:04.
<script setup lang="ts">import { z } from 'zod';import TimeField from './TimeField.vue';
const schema = z.string().refine( (value) => { const time = new Date(`1970-01-01T${value}`);
return ( time.getHours() >= 0 && time.getHours() <= 10 && time.getMinutes() >= 0 && time.getMinutes() <= 4 ); }, { message: 'Time must be between 00:00 and 10:04', },);</script>
<template> <TimeField label="Standard Schema" :schema="schema" /></template>
Mixed Validation
All time fields created with Formwerk support mixed validation, which means you can use both HTML constraints and Standard Schema validation to validate the field, and they work seamlessly together.
<script setup lang="ts">import { z } from 'zod';import TimeField from './TimeField.vue';
const schema = z.string().refine( (value) => { const time = new Date(`1970-01-01T${value}`);
return ( time.getHours() >= 0 && time.getHours() <= 10 && time.getMinutes() >= 0 && time.getMinutes() <= 4 ); }, { message: 'Time must be between 00:00 and 10:04', },);</script>
<template> <TimeField label="Mixed Validation" :schema="schema" required /></template>
Usage
Disabled
Use the disabled
prop to disable the time field. Disabled time fields are not validated and are not submitted.
<script setup lang="ts">import TimeField from './TimeField.vue';</script>
<template> <TimeField label="Disabled" value="10:04" disabled /></template>
Readonly
Use the readonly
prop to make the time field read-only. Read-only time fields are still validated and submitted.
If you need to prevent the user from interacting with the field while still allowing it to submit, consider using readonly
instead.
<script setup lang="ts">import TimeField from './TimeField.vue';</script>
<template> <TimeField label="Readonly" value="10:04" readonly /></template>
Min and Max
You can pass min
and max
props to set the minimum and maximum times that can be entered.
<script setup lang="ts">import TimeField from './TimeField.vue';</script>
<template> <TimeField label="Min and Max" min="00:00" max="10:04" /></template>
Format Options
You can pass a partial of the Intl.DateTimeFormatOptions
to the formatOptions
prop to customize the time field’s display format. For example, we can switch to 12-hour format.
<script setup lang="ts">import TimeField from './TimeField.vue';</script>
<template> <TimeField label="Format Options" :formatOptions="{ hour: '2-digit', minute: '2-digit', hour12: true, }" /></template>
Show Seconds
Not only you can change the 12-hour format to 24-hour format, you can also control the visibility of the seconds segment with the formatOptions
as well.
<script setup lang="ts">import TimeField from './TimeField.vue';</script>
<template> <TimeField label="With Seconds" :formatOptions="{ second: '2-digit', }" /></template>
Examples
These are some examples of time fields built with Formwerk.
API
useTimeField
Props
These are the properties that can be passed to the useTimeField
composable.
Name | Type | Description |
---|---|---|
description | The description to use for the field. | |
disabled | Whether the field is disabled. | |
formatOptions | Simplify<Pick<Intl.DateTimeFormatOptions, "hour" | "minute" | "second" | "dayPeriod" | "timeZone" | "hour12">> | A partial of the Intl.DateTimeFormatOptions to use for the field, used to format the time value. |
label | The label to use for the field. | |
locale | The locale to use for the field. | |
max | The maximum value to use for the field. String format: HH:MM:SS | |
min | The minimum value to use for the field. String format: HH:MM:SS | |
modelValue | The model value to use for the field. | |
name | The name to use for the field. | |
placeholder | The placeholder to use for the field. | |
readonly | Whether the field is readonly. | |
required | Whether the field is required. | |
schema | The schema to use for the field. | |
value | The value to use for the field. |
Returns
These are the properties in the object returned by the useTimeField
composable.
Name | Type | Description |
---|---|---|
controlProps | Ref<{ 'aria-disabled': true; 'aria-invalid': boolean; 'aria-errormessage': string; 'aria-describedby': string; 'aria-label'?: string; 'aria-labelledby'?: string; id: string; role: string; }> | The props to use for the control element. |
descriptionProps | The props to use for the description element. | |
direction | The direction of the field. | |
displayError | Display the error message for the field. | |
errorMessage | The error message for the field. | |
errorMessageProps | Ref<{ 'aria-live': "polite"; 'aria-atomic': boolean; id: string; }> | The props to use for the error message element. |
errors | The errors for the field. | |
fieldValue | The value of the field. | |
isDirty | Whether the field is dirty. | |
isDisabled | Whether the field is disabled. | |
isTouched | Whether the field is touched. | |
isValid | Whether the field is valid. | |
labelProps | The props to use for the label element. | |
segments | The time segments, you need to render these with the `DateTimeSegment` component. | |
setErrors | (messages: Arrayable<string>) => void | Sets the errors for the field. |
setTouched | Sets the touched state for the field. | |
setValue | Sets the value for the field. | |
submitErrorMessage | The error message for the field from the last submit attempt. | |
submitErrors | The errors for the field from the last submit attempt. | |
validate | (mutate?: boolean) => Promise<ValidationResult<unknown>> | Validates the field. |
useDateSegment
Props
These are the properties that can be passed to the useDateSegment
composable.
Name | Type | Description |
---|---|---|
disabled | Whether the segment is disabled. | |
readonly | Whether the segment is readonly. | |
spinOnly | Forces the segment to behave strictly as a spin button, preventing any other interactions like input events. Useful for time fields and specific UX needs. | |
type | The type of the segment. | |
value | The text value of the segment. |
Returns
These are the properties in the object returned by the useDateTimeSegment
composable.
Name | Type | Description |
---|---|---|
key | ||
segmentProps |