Skip to content

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

KeyDescription
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

Label
Label
10
Date segment
:
04
Control Group
Help text
Description or Error Message

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 TimeField from './TimeField.vue';
</script>
<template>
<TimeField name="time" label="Time" />
</template>
<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:

NameTypeDescription
minDateThe minimum time that can be entered.
maxDateThe maximum time that can be entered.
requiredbooleanWhether 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.

NumberFlow

Showcase of a date/time field using the excellent `@number-flow/vue` to animate value transitions.

API

useTimeField

Props

These are the properties that can be passed to the useTimeField composable.

NameTypeDescription
descriptionThe description to use for the field.
disabledWhether 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.
labelThe label to use for the field.
localeThe locale to use for the field.
maxThe maximum value to use for the field. String format: HH:MM:SS
minThe minimum value to use for the field. String format: HH:MM:SS
modelValueThe model value to use for the field.
nameThe name to use for the field.
placeholderThe placeholder to use for the field.
readonlyWhether the field is readonly.
requiredWhether the field is required.
schemaThe schema to use for the field.
valueThe value to use for the field.

Returns

These are the properties in the object returned by the useTimeField composable.

NameTypeDescription
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.
descriptionPropsThe props to use for the description element.
directionThe direction of the field.
displayErrorDisplay the error message for the field.
errorMessageThe error message for the field.
errorMessageProps
Ref<{ 'aria-live': "polite"; 'aria-atomic': boolean; id: string; }>
The props to use for the error message element.
errorsThe errors for the field.
fieldValueThe value of the field.
isDirtyWhether the field is dirty.
isDisabledWhether the field is disabled.
isTouchedWhether the field is touched.
isValidWhether the field is valid.
labelPropsThe props to use for the label element.
segmentsThe time segments, you need to render these with the `DateTimeSegment` component.
setErrors
(messages: Arrayable<string>) => void
Sets the errors for the field.
setTouchedSets the touched state for the field.
setValueSets the value for the field.
submitErrorMessageThe error message for the field from the last submit attempt.
submitErrorsThe 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.

NameTypeDescription
disabledWhether the segment is disabled.
readonlyWhether the segment is readonly.
spinOnlyForces the segment to behave strictly as a spin button, preventing any other interactions like input events. Useful for time fields and specific UX needs.
typeThe type of the segment.
valueThe text value of the segment.

Returns

These are the properties in the object returned by the useDateTimeSegment composable.

NameTypeDescription
key
segmentProps