Open UI

TimeField

A time field allows users to enter and edit time values using a keyboard. Each part of a time value is displayed in an individually editable segment.

import { Time } from "@internationalized/date"import { TimeField } from "@opengovsg/oui"export const Example = () => {  return <TimeField label="Event time" placeholderValue={new Time(9, 0)} />}

Usage

Use TimeField for time-only input. Use DateField or DatePicker when you also need a date.

import { TimeField } from "@opengovsg/oui"
<TimeField label="Meeting time" />

Alternatively, install the component as local source via the shadcn CLI:

npx shadcn@latest add https://oui.open.gov.sg/r/time-field.json
pnpm dlx shadcn@latest add https://oui.open.gov.sg/r/time-field.json
npx shadcn@latest add https://oui.open.gov.sg/r/time-field.json
bunx --bun shadcn@latest add https://oui.open.gov.sg/r/time-field.json

TimeField renders individually editable segments for each part of a time value (hour, minute, second), built on React Aria's TimeField. Time values use Time objects from the @internationalized/date package.

If no visible label is provided, an aria-label or aria-labelledby prop must be passed to identify the field to assistive technology.

Examples

Sizes

Use the size prop to change the size of the field. Defaults to "md"

import { Time } from "@internationalized/date"import { TimeField } from "@opengovsg/oui"export const Example = () => {  return (    <div className="flex w-full flex-col gap-4">      <TimeField        size="xs"        defaultValue={new Time(9, 30)}        label="Event time (xs)"      />      <TimeField        size="sm"        defaultValue={new Time(9, 30)}        label="Event time (sm)"      />      <TimeField        size="md"        defaultValue={new Time(9, 30)}        label="Event time (md)"      />    </div>  )}

Disabled

import { Time } from "@internationalized/date"import { TimeField } from "@opengovsg/oui"export const Example = () => {  return (    <TimeField      isDisabled      label="Event time"      placeholderValue={new Time(9, 0)}    />  )}

Read Only

import { Time } from "@internationalized/date"import { TimeField } from "@opengovsg/oui"export const Example = () => {  return (    <TimeField isReadOnly label="Event time" defaultValue={new Time(9, 30)} />  )}

With Description

You can add a description to the input by passing the description property.

import { Time } from "@internationalized/date"import { TimeField } from "@opengovsg/oui"export const Example = () => {  return (    <TimeField      label="Event time"      placeholderValue={new Time(9, 0)}      description="Please enter the event start time."    />  )}

With Error Message

You can combine the isInvalid and errorMessage properties to show an invalid input.

import { TimeField } from "@opengovsg/oui"export const Example = () => {  return (    <TimeField      label="Event time"      isInvalid      errorMessage="Please enter a valid time"    />  )}

Controlled

You can use the value and onChange properties to control the input value.

import { useState } from "react"import { Time } from "@internationalized/date"import { TimeField } from "@opengovsg/oui"export const Example = () => {  const [value, setValue] = useState<Time | null>(new Time(11, 45))  return (    <div className="flex w-full flex-col gap-y-2">      <TimeField        label="Event time (controlled)"        value={value}        onChange={setValue}      />      <p className="text-default-500 text-sm">        Selected time: {value ? value.toString() : "--"}      </p>    </div>  )}

Granularity

The granularity prop allows you to control the smallest unit that is displayed by the TimeField. By default, the value is displayed with "minute" granularity.

import { Time } from "@internationalized/date"import { TimeField } from "@opengovsg/oui"export const Example = () => {  return (    <div className="flex w-full flex-col gap-4">      <TimeField        defaultValue={new Time(9, 30)}        granularity="hour"        label="Hour only"      />      <TimeField        defaultValue={new Time(9, 30)}        granularity="minute"        label="Hour and minute"      />      <TimeField        defaultValue={new Time(9, 30, 15)}        granularity="second"        label="Hour, minute, and second"      />    </div>  )}

Min Time And Max Time

The minValue and maxValue props can also be used to ensure the value is within a specific range.

@internationalized/date includes the Time class for representing time values.

npm install @internationalized/date
import { Time } from "@internationalized/date"
import { Time } from "@internationalized/date"import { Form } from "react-aria-components"import { Button, TimeField } from "@opengovsg/oui"export const Example = () => {  return (    <Form className="flex w-full flex-col gap-4">      <TimeField        defaultValue={new Time(8, 0)}        minValue={new Time(9, 0)}        label="Min time (9:00 AM)"      />      <TimeField        defaultValue={new Time(18, 0)}        maxValue={new Time(17, 0)}        label="Max time (5:00 PM)"      />      <Button className="w-fit" type="submit">        Submit      </Button>    </Form>  )}

Hourly Cycle

By default, TimeField displays times in either 12 or 24 hour format depending on the user's locale. However, this can be overridden using the hourCycle prop if needed for a specific usecase. This example forces TimeField to use 24-hour time, regardless of the locale.

import { Time } from "@internationalized/date"import { TimeField } from "@opengovsg/oui"export const Example = () => {  return (    <TimeField      defaultValue={new Time(15, 30)}      hourCycle={24}      label="Appointment time"    />  )}

Validation

See the Forms guide for end-to-end patterns including React Hook Form + Zod.

  • isRequired — Marks the field as required; validation fires on form submit (native) or immediately (aria).
  • validate — A custom function (value: TimeValue | null) => ValidationError | true | null | undefined. Return a string to show as the error.
  • isInvalid + errorMessage — Use together for realtime validation feedback. errorMessage accepts a ReactNode or a function (validation: ValidationResult) => ReactNode.
  • minValue / maxValue — Constrain the accepted time range; entering a time outside the range marks the field as invalid.
  • validationBehavior"native" (default) blocks form submission when invalid; "aria" marks the field via ARIA attributes only (needed for React Hook Form).

Events

  • onChange — Called when the time value changes, receiving the new TimeValue (or null).
  • onFocus / onBlur — Called when the field receives or loses focus.
  • onFocusChange — Called when the focus state changes, receiving a boolean.
  • onKeyDown / onKeyUp — Called on keyboard events.

Accessibility

  • Each time segment is a focusable <div role="spinbutton"> — assistive technologies announce the segment name and current value.
  • Navigating between segments uses Tab / Shift+Tab; typing digits updates the focused segment; Arrow Up / Down increments or decrements.
  • The label, description, and error message are linked via aria-labelledby and aria-describedby.
  • If no visible label is provided, pass aria-label or aria-labelledby.

Slots

Slots are named regions of the component you can target with custom Tailwind classes via the classNames prop. Each slot below corresponds to a key on the classNames object.

  • base: Input wrapper, it handles alignment, placement, and general appearance.
  • label: Label of the time-field, it is the one that is displayed above, inside or left of the time-field.
  • input: The time-input element.
  • description: The description of the time-field.
  • error: The error message of the time-field.

Props

TimeField

PropTypeDefaultDescription
labelReactNode-The visible label for the field
descriptionReactNode | null-Helper text shown below the field
errorMessageReactNode | ((validation: ValidationResult) => ReactNode)-Error message displayed when the field is invalid
valueTimeValue | null-The current time value (controlled)
defaultValueTimeValue | null-The default time value (uncontrolled)
onChange(value: TimeValue | null) => void-Called when the time value changes
isDisabledbooleanfalseWhether the field is disabled
isReadOnlybooleanfalseWhether the field is read-only
isRequiredbooleanfalseWhether the field is required
isInvalidbooleanfalseWhether the field should display as invalid
validate(value: TimeValue | null) => ValidationError | true | null | undefined-Custom validation function
validationBehavior"native" | "aria""native"Whether to use native HTML form validation or ARIA-only
minValueTimeValue-The minimum accepted time
maxValueTimeValue-The maximum accepted time
size"xs" | "sm" | "md""md"The size of the field
variant"outline" | "unstyled""outline"The visual variant of the input
classNamesSlotsToClasses<"base" | "label" | "input" | "description" | "error">-Custom Tailwind classes for each field slot
inputPropsPartial<DateInputProps>-Additional props spread to the underlying DateInput element (escape hatch)

Inherits all remaining props from React Aria's TimeField. Notable ones: autoFocus, granularity, hourCycle, hideTimeZone, placeholderValue.

On this page