TimePicker
A time of day in the browser's own time field, styled as a control, with steps, limits and suggestions.
Basic
The browser's time field in the control chrome. Arrows step a segment; the hour cycle follows your locale.
value: 10:30; changes: 0
Code
<TimePicker value={meeting.start} />Usage
import { TimePicker, zonedTime } from "@elements/ui/time-picker";
<TimePicker value={meeting.start} />
<TimePicker value={alarm.at} seconds min="06:00" max="22:00" required />
<TimePicker value={slot.at} step={900} options={["09:00", "09:15", "09:30"]} />
// A day and a time in a zone as one instant; daylight saving gaps move forward.
let at = zonedTime(event.date, event.time, "America/New_York");Examples
Seconds
seconds adds a seconds segment.
value: 10:30:00
Code
<TimePicker value={timer.at} seconds />Start and end
min and max bound each field; the end cannot come before the start.
Code
<Field label="Start time">
<TimePicker value={shift.start} min="06:00" max="22:00" />
</Field>
<Field label="End time">
<TimePicker value={shift.end} min={shift.start} max="22:00" />
</Field>Steps and suggestions
step={900} accepts quarter hours; options offer them as a list. indicator shows the browser's picker button.
value: none
Code
<TimePicker value={slot.at} step={900} options={["09:00", "09:15", "09:30"]} indicator />
<TimePicker step={900} options={["09:00", "09:15", "09:30"]} /> <!-- options alone: the list still opens -->In a form
name submits the value; required blocks an empty submit.
not submitted
Code
<form onsubmit={(e) => save(e)}>
<TimePicker name="alarm" value={form.alarm} required attrs={{ autocomplete: "off" }} />
<Button type="submit">Save</Button>
</form>Date and time
A DatePicker and a TimePicker side by side make one moment.
Pick a date and a time.
Code
<DatePicker value={event.date} />
<TimePicker value={event.time} seconds label="Time" />Calendar with start and end times
A Calendar in a card with start and end time fields under it, a date and time picker.
2026-10-29 10:30:00 to 12:30:00
Code
<div class="card">
<Calendar value={event.date} />
<footer>
<Field label="Start time"><TimePicker value={event.start} seconds /></Field>
<Field label="End time"><TimePicker value={event.end} seconds min={event.start} /></Field>
</footer>
</div>A range with its own times
Each end of a date range takes its own time: check in at 09:00, check out at 17:00.
Pick a range: it starts at the start time and ends at the end time.
Code
<DateRangePicker from={stay.from} to={stay.to} />
<Field label="Check-in"><TimePicker value={stay.fromTime} /></Field>
<Field label="Check-out"><TimePicker value={stay.toTime} /></Field>Time zones and daylight saving
zonedTime reads a picked day and time in a zone as one instant to store. On a daylight saving night a time can be missing (it moves forward by the gap) or happen twice (the first is taken), as a native Date resolves it.
2026-03-08T03:30:00-04:00 = 2026-03-08T07:30:00.000Z; 02:30 does not exist that night, so it moves to 03:30
Code
<DatePicker value={event.date} />
<TimePicker value={event.time} />
<NativeSelect value={event.zone} options={ZONES} />
let at = zonedTime(event.date, event.time, event.zone);
// at.iso: the UTC instant to store; at.status: "gap" or "ambiguous" on a daylight saving nightAcross midnight
A min later than max wraps past midnight: a night shift from 22:00 to 06:00. onchange is the native change event.
value: 23:30; no change event yet
Code
<TimePicker value={shift.at} min="22:00" max="06:00" /> <!-- crosses midnight -->Office hours
refuseOutOfRange keeps the last time inside 09:00 to 18:00: a time outside is shown invalid and never reaches the value, and with seconds 18:00:00 is still inside. attrs carries aria-describedby to the field, so the hours are read with it.
Office hours are 09:00 to 18:00.
value: 10:00
value: 10:00:00
Code
<TimePicker value={meeting.at} min="09:00" max="18:00" refuseOutOfRange
attrs={{ "aria-describedby": "office-hours" }} />
<p id="office-hours">Office hours are 09:00 to 18:00.</p>
<TimePicker value={meeting.at} seconds min="09:00" max="18:00" refuseOutOfRange /> <!-- 18:00:00 is inside -->Steps count from min
With min 09:05 and step={900}, the valid times are 09:05, 09:20, 09:35 and so on: the browser counts steps from min, as MDN describes.
value: 09:20
Code
<TimePicker value={slot.at} min="09:05" step={900} /> <!-- 09:05, 09:20, 09:35 ... -->Any time
step="any" accepts every time, so no value is ever off the step, as MDN describes. seconds shows the seconds segment.
value: 10:30:17
Code
<TimePicker value={log.at} step="any" seconds /> <!-- any second is valid -->Read only and required
readOnly turns required off: an empty read only field is valid and never blocks a submit, as a native read only input.
Code
<TimePicker name="locked" value="" readOnly required /> <!-- valid: readOnly turns required off -->Sizes and states
Three sizes, disabled, read only, invalid and without the icon.
Code
<TimePicker size="sm" />
<TimePicker size="lg" />
<TimePicker value="09:30" disabled />
<TimePicker value="09:30" readOnly />
<TimePicker invalid />
<TimePicker icon={false} />