Date & time
Time
A time of day is a wall clock: no date, no time zone. 14:30 is 14:30 wherever it is read — which is why this control has no zone picker and no conversion.
Examples
Segmented field, coarse popover
The segments give exact keyboard editing; the popover gives a stepped grid. Both write the same Time value, and the step only ever constrains the grid — typing 14:07 is still allowed.
24-hour, 15-minute grid.
granularity="second", 5-second grid.
No hourCycle — the locale decides (this is 12-hour for en-US).
canonical values: 14:30:00, 09:45:30 · rendered as 14:30
<FieldGroup columns={3}>
<Field label="Session close" description="24-hour, 15-minute grid.">
<TimePicker value={time} onValueChange={setTime} hourCycle={24} minuteStep={15} locale="en-US" />
</Field>
<Field label="With seconds" description="Second granularity, on a five-second grid.">
<TimePicker value={precise} onValueChange={setPrecise} hourCycle={24} granularity="second" secondStep={5} />
</Field>
</FieldGroup>A bounded window
`minValue` and `maxValue` bound the popover and validate the typed segments. They are a constraint on the value, not a substitution for one: the control never nudges an out-of-range entry into range behind the user's back.
Opens at 09:30 and closes at 16:00.
The same bound, twelve-hour display.
<Field label="Trading session" description="Opens at 09:30 and closes at 16:00.">
<TimePicker
value={time}
onValueChange={setTime}
hourCycle={24}
minuteStep={5}
minValue={new Time(9, 30)}
maxValue={new Time(16, 0)}
locale="en-US"
/>
</Field>Clocks are not time zones
If a product needs “14:30 in Berlin”, that is a zoned value — `DateTimePicker` or `ZonedDateTimePicker`. A time-only control that silently attached a zone would be lying about what it stores.
Time (this control)
14:30 — a wall clock. No zone, no DST, no date.
ZonedDateTime
2026-09-18T14:30[Europe/Berlin] — an instant with a rule attached.
This machine
The zone is read in the browser once the page is interactive.
// A wall clock: no zone, no DST, no date.
<TimePicker value={time} onValueChange={setTime} hourCycle={24} />
// "14:30 in Berlin" is a zoned value, and a different control.
<ZonedDateTimePicker value={zoned} onValueChange={setZoned} timeZone="Europe/Berlin" />States
The same shell and the same states as every other control: hover steps one border, focus draws the ring, invalid is negative and points at its message, disabled leaves the tab order.
Empty
Filled
Cut-off must be before 16:00
Invalid
Disabled
Read-only
Small
<TimePicker aria-label="Empty time" hourCycle={24} locale="en-US" />
<TimePicker aria-label="Filled time" hourCycle={24} locale="en-US" defaultValue={new Time(9, 30)} />
<Field label="Cut-off" error="Cut-off must be before 16:00">
<TimePicker hourCycle={24} locale="en-US" defaultValue={new Time(18, 0)} />
</Field>
<TimePicker aria-label="Disabled time" hourCycle={24} locale="en-US" disabled />
<TimePicker aria-label="Read-only time" hourCycle={24} locale="en-US" readOnly defaultValue={new Time(9, 30)} />
<TimePicker aria-label="Small time" hourCycle={24} locale="en-US" size="sm" defaultValue={new Time(9, 30)} />Usage
import { Time, TimePicker } from "@luc/ui";<TimePicker aria-label="Session close" value={time} onValueChange={setTime} hourCycle={24} />The value is a `Time` — hours, minutes and optionally seconds with no zone. `hourCycle` is the only display decision; leave it off and the locale decides, which is what a product with more than one region should do.
API
Read from the published TypeScript API, so a prop that is not listed here does not exist and a default that is listed is the one the compiler sees.
| Export | Kind | Signature |
|---|---|---|
TimePicker | 60 documented props | ({ onValueChange, size, invalid, disabled, readOnly, locale, minuteStep, granularity, secondStep, showNowAction, className, value, ...props }: TimePickerProps) => import("@types/react").JSX.Element |
TimePicker props
Type: TimePickerProps
Values the compiler reads from the declaration.
| Prop | Type | Default | Notes |
|---|---|---|---|
aria-describedby?inherited | string | undefined | — | Identifies the element (or elements) that describes the object. |
aria-details?inherited | string | undefined | — | Identifies the element (or elements) that provide a detailed, extended description for the object. |
aria-label? | string | undefined | — | Defines a string value that labels the current element. |
aria-labelledby?inherited | string | undefined | — | Identifies the element (or elements) that labels the current element. |
autoFocus?inherited | boolean | undefined | — | Whether the element should receive focus on render. |
className? | string | undefined | — | |
defaultValue?inherited | Time | null | undefined | — | The default value (uncontrolled). |
dir?inherited | string | undefined | — | |
disabled? | boolean | undefined | — | |
form?inherited | string | undefined | — | The `<form>` element to associate the input with. The value of this attribute must be the id of a `<form>` in the same document. See [MDN](https://developer.mozilla.org/en-US/docs… |
granularity? | "minute" | "second" | undefined | — | Also pick seconds; the field then shows a seconds segment. |
hidden?inherited | boolean | undefined | — | |
hideTimeZone?inherited | boolean | undefined | — | Whether to hide the time zone abbreviation. |
hourCycle?inherited | 24 | 12 | undefined | — | Whether to display the time in 12 or 24 hour format. By default, this is determined by the user's locale. |
id?inherited | string | undefined | — | The element's unique identifier. See [MDN](https://developer.mozilla.org/en-US/docs/Web/HTML/Global_attributes/id). |
inert?inherited | boolean | undefined | — | |
invalid? | boolean | undefined | — | |
isRequired?inherited | boolean | undefined | — | Whether user input is required on the input before form submission. |
lang?inherited | string | undefined | — | |
locale? | string | undefined | — | |
maxValue?inherited | TimeValue | null | undefined | — | The maximum allowed time that a user may select. |
minuteStep? | number | undefined | — | Minute granularity in the coarse picker. Default `5`. |
minValue?inherited | TimeValue | null | undefined | — | The minimum allowed time that a user may select. |
name?inherited | string | undefined | — | The name of the input element, used when submitting an HTML form. See [MDN](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/input#htmlattrdefname). |
onAnimationEnd?inherited | AnimationEventHandler<HTMLDivElement> | undefined | — | |
onAnimationEndCapture?inherited | AnimationEventHandler<HTMLDivElement> | undefined | — | |
onAnimationIteration?inherited | AnimationEventHandler<HTMLDivElement> | undefined | — | |
onAnimationIterationCapture?inherited | AnimationEventHandler<HTMLDivElement> | undefined | — | |
onAnimationStart?inherited | AnimationEventHandler<HTMLDivElement> | undefined | — | |
onAnimationStartCapture?inherited | AnimationEventHandler<HTMLDivElement> | undefined | — | |
onAuxClick?inherited | MouseEventHandler<HTMLDivElement> | undefined | — | |
onAuxClickCapture?inherited | MouseEventHandler<HTMLDivElement> | undefined | — | |
onBlur?inherited | ((e: FocusEvent<Element, Element>) => void) | undefined | — | Handler that is called when the element loses focus. |
onChange?inherited | ((value: Time | null) => void) | undefined | — | Handler that is called when the value changes. |
onClick?inherited | MouseEventHandler<HTMLDivElement> | undefined | — | |
onClickCapture?inherited | MouseEventHandler<HTMLDivElement> | undefined | — | |
onContextMenu?inherited | MouseEventHandler<HTMLDivElement> | undefined | — | |
onContextMenuCapture?inherited | MouseEventHandler<HTMLDivElement> | undefined | — | |
onDoubleClick?inherited | MouseEventHandler<HTMLDivElement> | undefined | — | |
onDoubleClickCapture?inherited | MouseEventHandler<HTMLDivElement> | undefined | — | |
onFocus?inherited | ((e: FocusEvent<Element, Element>) => void) | undefined | — | Handler that is called when the element receives focus. |
onFocusChange?inherited | ((isFocused: boolean) => void) | undefined | — | Handler that is called when the element's focus status changes. |
onGotPointerCapture?inherited | PointerEventHandler<HTMLDivElement> | undefined | — | |
onGotPointerCaptureCapture?inherited | PointerEventHandler<HTMLDivElement> | undefined | — | |
onKeyDown?inherited | ((e: KeyboardEvent) => void) | undefined | — | Handler that is called when a key is pressed. |
onKeyUp?inherited | ((e: KeyboardEvent) => void) | undefined | — | Handler that is called when a key is released. |
onLostPointerCapture?inherited | PointerEventHandler<HTMLDivElement> | undefined | — | |
onLostPointerCaptureCapture?inherited | PointerEventHandler<HTMLDivElement> | undefined | — | |
onMouseDown?inherited | MouseEventHandler<HTMLDivElement> | undefined | — | |
onMouseDownCapture?inherited | MouseEventHandler<HTMLDivElement> | undefined | — | |
onMouseEnter?inherited | MouseEventHandler<HTMLDivElement> | undefined | — | |
onMouseLeave?inherited | MouseEventHandler<HTMLDivElement> | undefined | — | |
onMouseMove?inherited | MouseEventHandler<HTMLDivElement> | undefined | — | |
onMouseMoveCapture?inherited | MouseEventHandler<HTMLDivElement> | undefined | — | |
onMouseOut?inherited | MouseEventHandler<HTMLDivElement> | undefined | — | |
onMouseOutCapture?inherited | MouseEventHandler<HTMLDivElement> | undefined | — | |
onMouseOver?inherited | MouseEventHandler<HTMLDivElement> | undefined | — | |
onMouseOverCapture?inherited | MouseEventHandler<HTMLDivElement> | undefined | — | |
onMouseUp?inherited | MouseEventHandler<HTMLDivElement> | undefined | — | |
onMouseUpCapture?inherited | MouseEventHandler<HTMLDivElement> | undefined | — |
Guidelines
Do
- Leave `hourCycle` unset and let the locale decide, unless the product has one house convention; a 12-hour and a 24-hour clock are two different readers, not two qualities.
- Match the popover to the value with `minuteStep` (and `secondStep` when `granularity="second"`), so the coarse grid offers the times the field can actually represent.
- Bound a session with `minValue`/`maxValue` when the domain has opening hours, and let the field report the violation instead of correcting it silently.
Don't
- Store a time of day as a full timestamp. A wall clock with a date attached invites a zone question the value cannot answer.
- Attach a zone to a `Time`: “14:30 in Berlin” is a `ZonedDateTime`, and `ZonedDateTimePicker` is the control for it.
- Treat the step as validation — typing 14:07 is allowed on a five-minute grid, because the step constrains the popover rather than the value.
Accessibility
The field is one group with labelled hour, minute and (when asked for) second segments: a single tab stop, arrow keys between segments, Up and Down to step a segment, and digits typed straight in. The popover's columns are listboxes with roving focus, and the Now action is a real button rather than a click target on the value.
Related