Skip to content
v2.5.0
English
Русский
Documentation

TimeField

TimeField is a compound component for masked time entry.
time-field.tsx · 8 lines
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
Tested in
20 tests

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

  • TimeFieldProps
  • TimeFieldInputProps

Minimal Composition

minimal-composition.tsx · 9 lines
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 provider
  • TimeFieldInput: masked text input

Optional Parts

  • no extra public parts beyond the input

Composition Order

  1. TimeField
  2. TimeFieldInput

Variants

  • Format: HH:mm or HH:mm:ss
  • Bounds: optional min and max
  • State: controlled or uncontrolled value on the root, disabled through input props

Edge Cases

  • Format, min, and max must 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, DateTimeField is 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.

reminder-time.tsx · 13 lines
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 ''.

controlled-time-value.tsx · 42 lines
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.

submitting-a-time.tsx · 36 lines
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.

opening-hours-table.tsx · 49 lines
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.

time-outside-allowed-range.tsx · 44 lines
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.

read-only-schedule-slot.tsx · 13 lines
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.

disabled-by-fieldset.tsx · 42 lines
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.

bound-time-window.tsx · 21 lines
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.

quarter-hour-step.tsx · 13 lines
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.

seconds-precision-rule.tsx · 20 lines
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

  1. TimeField
  2. TimeFieldInput

Valid Composition Patterns

minimal-composition.tsx · 9 lines
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.