TimeField
Usage
Committed wall time: 09:30
const [time, setTime] = useState<string | null>("09:30");
const [timeValid, setTimeValid] = useState(true);
<TimeField
id="briefing-time"
label="Briefing time"
value={time}
onValueChange={setTime}
onDraftValidityChange={setTimeValid}
min="08:00"
max="18:00"
required
/>
<Button disabled={!timeValid}>Review</Button>
Wall-time values
The controlled value is an exact 24-hour HH:mm string from 00:00 through 23:59, or null. It carries no date or time zone. min and max are inclusive times in the same format. Bounds must be in ascending order; represent an overnight schedule with separate dates or instants.
Connect onDraftValidityChange to the form's Review and Save controls. It reports initial and changed validity. A partial, invalid, out-of-bounds, or required-empty edit reports false even when the public value still contains an earlier valid time. Optional, fully empty fields are valid. Moving focus does not clear a rejected edit.
To discard an unfinished draft while retaining the same controlled value, change the component's React key. An unchanged value alone cannot signal that the host intends to discard local editing.
Keyboard and focus
React Aria labels the hour and minute segments and provides arrow-key editing and movement between segments. The id and forwarded HTMLDivElement ref identify the time-control group. Focusing the group enters the first segment, which lets FormErrorSummary target it. disabled and readOnly prevent changes.
The callback receives (valid, { pending }). Use valid to gate Review/Save and pending to track unfinished local editing for leave protection or draft persistence. A pristine required-empty field is invalid but has pending: false; partial input has pending: true. Malformed supplied values also remain clean until edited. Existing callbacks that accept only valid continue to work.
Props
| PROP | TYPE | DEFAULT | REQUIRED |
|---|---|---|---|
label | string | — | yes |
valueHH:mm from 00:00 through 23:59, or null. | string | — | yes |
onValueChangeRequests a complete valid time or null after clearing. | function | — | yes |
onDraftValidityChangeCallback (valid, { pending }). Pending identifies uncommitted local edits; pristine required-empty or malformed supplied values are invalid but not pending. Reports initial and changed validity. False for partial, out-of-bounds, or required-empty drafts even while value still contains an earlier valid time. Block Review/Save while false. | function | — | no |
minInclusive HH:mm lower bound. | string | — | no |
maxInclusive HH:mm upper bound. Overnight bounds are not supported. | string | — | no |
disabled | boolean | false | no |
readOnly | boolean | false | no |
required | boolean | false | no |
idID of the focusable time-control group. Focusing it or its forwarded HTMLDivElement ref enters the first segment. | string | — | no |
className | string | — | no |
Accessibility
- Invalid drafts expose invalid state and a readable error. An edit outside
minormaxnames the bound it crossed: "Choose a time at or before 18:00." - Segments use tabular numerals, text of at least 11px, 24 CSS pixel minimum targets, and the shared focus indicator.
- Time validity remains separate from the last complete value, so unfinished edits can block submission.
Manifest entry
<TimeField id="start-time" label="Start time" value={time} onValueChange={setTime} onDraftValidityChange={setTimeValid} required />View agent manifest
{
"description": "Controlled 24-hour wall-time field with segmented editing and explicit draft validity. It carries no date or time zone.",
"props": {
"label": {
"type": "string",
"required": true
},
"value": {
"type": "string",
"required": true,
"description": "HH:mm from 00:00 through 23:59, or null."
},
"onValueChange": {
"type": "function",
"required": true,
"description": "Requests a complete valid time or null after clearing."
},
"onDraftValidityChange": {
"type": "function",
"description": "Callback (valid, { pending }). Pending identifies uncommitted local edits; pristine required-empty or malformed supplied values are invalid but not pending. Reports initial and changed validity. False for partial, out-of-bounds, or required-empty drafts even while value still contains an earlier valid time. Block Review/Save while false."
},
"min": {
"type": "string",
"description": "Inclusive HH:mm lower bound."
},
"max": {
"type": "string",
"description": "Inclusive HH:mm upper bound. Overnight bounds are not supported."
},
"disabled": {
"type": "boolean",
"default": false
},
"readOnly": {
"type": "boolean",
"default": false
},
"required": {
"type": "boolean",
"default": false
},
"id": {
"type": "string",
"description": "ID of the focusable time-control group. Focusing it or its forwarded HTMLDivElement ref enters the first segment."
},
"className": {
"type": "string"
}
},
"composable_with": [
"FormSection",
"FormErrorSummary",
"DateTimePicker"
],
"never_inside": [
"Button"
],
"example": "<TimeField id=\"start-time\" label=\"Start time\" value={time} onValueChange={setTime} onDraftValidityChange={setTimeValid} required />"
}