import { Label, TimeField, TimeFieldInput } from '@vega-ui/react';
<div>
<Label htmlFor='preview-time'>Reminder</Label>
<TimeField defaultValue='09:30'>
<TimeFieldInput id='preview-time' placeholder='hh:mm' />
</TimeField>
</div>- Browser support
- Widely available
- Chrome 111
- Edge 111
- Firefox 121
- Safari 15.4
- Safari on iOS 15.4
- Bundle size
- :has()
- Oklab and OkLCh — comes from the design tokens
- Logical properties
- :focus-visible
- :where()
Summary
TimeField is a compound component for masked time entry. It builds on TextField and adds format, min/max, and step constraints for time strings when the product value is time-only.
Imports
import {
TimeField,
TimeFieldInput,
type TimeFieldInputProps,
type TimeFieldProps,
} from '@vega-ui/react';Exported Types
TimeFieldPropsTimeFieldInputProps
Minimal Composition
import { TimeField, TimeFieldInput } from '@vega-ui/react';
export default function MinimalComposition() {
return (
<TimeField>
<TimeFieldInput aria-label='Reminder time' placeholder='hh:mm' />
</TimeField>
);
}Required Parts
TimeField: root time constraints and format providerTimeFieldInput: masked text input
Optional Parts
- no extra public parts beyond the input
Composition Order
TimeFieldTimeFieldInput
Variants
- Format:
HH:mmorHH:mm:ss - Bounds: optional
minandmax - State: controlled or uncontrolled
valueon the root, disabled through input props
Edge Cases
- Format,
min, andmaxmust agree with each other. - Partial typed values should not be treated as complete times too early.
- Users need clear 24-hour format expectations in product copy.
- If the feature really stores a timestamp,
DateTimeFieldis usually the clearer abstraction than splitting date and time policy ad hoc.
Common Mistakes
- Assuming locale formatting instead of documenting the expected mask.
- Passing format-specific bounds that do not match the chosen format.
- Treating incomplete masked text as valid submission data.
Examples
Basic
Basic: daily reminder time
A labelled field for a 24-hour time. defaultValue on the root is an HH:mm string, and the helper text tells users which clock and time zone the time is in.
import { HelperText, Label, TimeField, TimeFieldInput } from '@vega-ui/react';
export default function ReminderTime() {
return (
<div style={{ display: 'grid', gap: 'var(--spacing-3)', width: '100%', maxWidth: 260 }}>
<Label htmlFor='daily-reminder'>Daily reminder</Label>
<TimeField defaultValue='08:30'>
<TimeFieldInput id='daily-reminder' placeholder='hh:mm' aria-describedby='daily-reminder-hint' />
</TimeField>
<HelperText id='daily-reminder-hint'>24-hour time in your local time zone.</HelperText>
</div>
);Controlled/Stateful
Controlled/Stateful: shift start and end
value and onChangeValue on the root keep both times in parent state. The start is the min of the end field, so an end before the start is clamped, and the shift length is computed from the two values. While a time is incomplete its value is ''.
import { useState } from 'react';
import { Fieldset, FieldsetLegend, Label, Text, TimeField, TimeFieldInput } from '@vega-ui/react';
const toMinutes = (time: string) => {
const [hours, minutes] = time.split(':').map(Number);
return hours * 60 + minutes;
};
const describeShift = (start: string, end: string) => {
if (!start || !end) return 'Enter both times';
Form/Integration
Form/Integration: submitting a time
For HH:mm and HH:mm:ss the typed text already is the value, so name on TimeFieldInput submits it as is. required and pattern block an empty or incomplete time, and the reset button restores defaultValue of the root.
import { FormEvent, useState } from 'react';
import { Button, HelperText, Label, Text, TimeField, TimeFieldInput } from '@vega-ui/react';
export default function QuietHoursForm() {
const [submitted, setSubmitted] = useState('');
const onSubmit = (event: FormEvent<HTMLFormElement>) => {
event.preventDefault();
const data = new FormData(event.currentTarget);
setSubmitted(JSON.stringify(Object.fromEntries(data)));
};
Layout/Overlay
Layout/Overlay: opening hours table
Small fields inside table cells, one row per weekday. Each input has no visible label of its own, so it gets an aria-label that names the day and the column.
import {
Table,
TableBody,
TableData,
TableHead,
TableHeading,
TableRow,
TimeField,
TimeFieldInput,
} from '@vega-ui/react';
const DAYS = [Error
Error: time outside an overnight window
min and max cannot describe a window that crosses midnight, so the field has no bounds and the submit handler checks the 22:00–06:00 rule itself. error and aria-invalid mark the field, and the next change of the value clears the error.
import { FormEvent, useState } from 'react';
import { Button, HelperText, Label, TimeField, TimeFieldInput } from '@vega-ui/react';
const isOvernight = (time: string) => time >= '22:00' || time <= '06:00';
export default function NightDeliveryForm() {
const [time, setTime] = useState('');
const [error, setError] = useState('');
const invalid = error !== '';
const onChangeValue = (value: string) => {
setTime(value);Disabled
Disabled: read-only schedule slot
readOnly on TimeFieldInput keeps the time focusable and submitted with the form while users cannot edit it.
import { HelperText, Label, TimeField, TimeFieldInput } from '@vega-ui/react';
export default function ReadOnlyScheduleSlot() {
return (
<div style={{ display: 'grid', gap: 'var(--spacing-3)', width: '100%', maxWidth: 260 }}>
<Label htmlFor='backup-slot'>Nightly backup</Label>
<TimeField defaultValue='03:00'>
<TimeFieldInput id='backup-slot' name='backupSlot' readOnly aria-describedby='backup-slot-hint' />
</TimeField>
<HelperText id='backup-slot-hint'>Set by your administrator.</HelperText>
</div>
);Disabled: disabled by a fieldset
TimeField has no disabled prop of its own: disable the input, or a surrounding <fieldset disabled> as here, where the switch turns both times off for an all-day event.
import { useState } from 'react';
import {
Fieldset,
FieldsetLegend,
Label,
Switch,
SwitchIndicator,
SwitchInput,
TimeField,
TimeFieldInput,
} from '@vega-ui/react';
Edge
Edge: bound time window
Like a native <input type="time">, a complete time outside min and max is clamped to the nearest bound, so 07:15 becomes 09:00. The readout shows the clamped value.
import { useState } from 'react';
import { HelperText, Label, Text, TimeField, TimeFieldInput } from '@vega-ui/react';
export default function BoundTimeWindow() {
const [callback, setCallback] = useState('');
return (
<div style={{ display: 'grid', gap: 'var(--spacing-8)', width: '100%', maxWidth: 260 }}>
<div style={{ display: 'grid', gap: 'var(--spacing-3)' }}>
<Label htmlFor='callback-time'>Call me back at</Label>
<TimeField min='09:00' max='18:00' value={callback} onChangeValue={setCallback}>
<TimeFieldInput id='callback-time' placeholder='hh:mm' aria-describedby='callback-time-hint' />Edge: quarter-hour step
step sets how far ArrowUp and ArrowDown move the minutes under the caret, wrapping from 45 to 00 without changing the hour. The hours always move by one, and typed minutes are kept as typed.
import { HelperText, Label, TimeField, TimeFieldInput } from '@vega-ui/react';
export default function QuarterHourStep() {
return (
<div style={{ display: 'grid', gap: 'var(--spacing-3)', width: '100%', maxWidth: 260 }}>
<Label htmlFor='class-start'>Class starts</Label>
<TimeField step={15} defaultValue='18:45'>
<TimeFieldInput id='class-start' aria-describedby='class-start-hint' />
</TimeField>
<HelperText id='class-start-hint'>Put the caret in the minutes and press ↑ or ↓.</HelperText>
</div>
);Edge: seconds precision
format='HH:mm:ss' adds a seconds segment; the value then has seconds, and min and max are written with seconds too.
import { useState } from 'react';
import { Label, Text, TimeField, TimeFieldInput } from '@vega-ui/react';
export default function SecondsPrecisionRule() {
const [runAt, setRunAt] = useState('00:00:30');
return (
<div style={{ display: 'grid', gap: 'var(--spacing-8)', width: '100%', maxWidth: 260 }}>
<div style={{ display: 'grid', gap: 'var(--spacing-3)' }}>
<Label htmlFor='cron-time'>Run the export at</Label>
<TimeField format='HH:mm:ss' min='00:00:30' max='23:59:30' value={runAt} onChangeValue={setRunAt}>
<TimeFieldInput id='cron-time' placeholder='hh:mm:ss' />Anatomy
Overview
TimeField is a small compound wrapper around TextField. The root provides format and bound context, and TimeFieldInput applies the masking behavior.
Required Parts
TimeField
Required. Owns format, min, max, and step context.
TimeFieldInput
Required. Renders the actual masked input.
Optional Parts
There are no extra public parts beyond the input.
Composition Order
TimeFieldTimeFieldInput
Valid Composition Patterns
import { TimeField, TimeFieldInput } from '@vega-ui/react';
export default function MinimalComposition() {
return (
<TimeField>
<TimeFieldInput aria-label='Reminder time' placeholder='hh:mm' />
</TimeField>
);
}Invalid Composition Patterns
Bounds that do not match the format
Example: format='HH:mm' with a seconds-based max.
Wrapper without TimeFieldInput
The root renders, but there is no masked control.