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.jsonpnpm dlx shadcn@latest add https://oui.open.gov.sg/r/time-field.jsonnpx shadcn@latest add https://oui.open.gov.sg/r/time-field.jsonbunx --bun shadcn@latest add https://oui.open.gov.sg/r/time-field.jsonTimeField 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/dateimport { 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.errorMessageaccepts aReactNodeor 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 newTimeValue(ornull).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-labelledbyandaria-describedby. - If no visible label is provided, pass
aria-labeloraria-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
| Prop | Type | Default | Description |
|---|---|---|---|
label | ReactNode | - | The visible label for the field |
description | ReactNode | null | - | Helper text shown below the field |
errorMessage | ReactNode | ((validation: ValidationResult) => ReactNode) | - | Error message displayed when the field is invalid |
value | TimeValue | null | - | The current time value (controlled) |
defaultValue | TimeValue | null | - | The default time value (uncontrolled) |
onChange | (value: TimeValue | null) => void | - | Called when the time value changes |
isDisabled | boolean | false | Whether the field is disabled |
isReadOnly | boolean | false | Whether the field is read-only |
isRequired | boolean | false | Whether the field is required |
isInvalid | boolean | false | Whether 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 |
minValue | TimeValue | - | The minimum accepted time |
maxValue | TimeValue | - | The maximum accepted time |
size | "xs" | "sm" | "md" | "md" | The size of the field |
variant | "outline" | "unstyled" | "outline" | The visual variant of the input |
classNames | SlotsToClasses<"base" | "label" | "input" | "description" | "error"> | - | Custom Tailwind classes for each field slot |
inputProps | Partial<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.