Skip to content
design
Components

Time picker

A time of day typed as the crew say it or chosen from hour and minute lists, with its reference beside it.

On this page
UTC
UTC
A night watch's start and end in UTC, typed or chosen from the hour and minute lists, the end bounded by the start.
Code
import { TimePicker } from "@bosunkit/components/time-picker";
import { useState, type Dispatch, type ReactElement, type SetStateAction } from "react";

export const description: string =
  "A night watch's start and end in UTC: each typed as the crew say it or chosen from the hour and minute lists with " +
  "the arrows, Page Up and Page Down; the end cannot come before the start, and the lists say which clock Now reads.";

export default function Showcase(): ReactElement {
  const [start, setStart]: [string, Dispatch<SetStateAction<string>>] = useState<string>("20:00");
  const [end, setEnd]: [string, Dispatch<SetStateAction<string>>] = useState<string>("");
  return (
    <div
      style={{
        width: "100%",
        maxWidth: 560,
        display: "grid",
        gridTemplateColumns: "repeat(auto-fit, minmax(160px, 1fr))",
        alignContent: "start",
        gap: "var(--space-lg)",
      }}
    >
      <TimePicker
        label="Watch starts"
        required
        timeReference="UTC"
        now={Date.UTC(2026, 9, 23, 19, 42)}
        value={start}
        onValueChange={setStart}
      />
      <TimePicker
        label="Watch ends"
        timeReference="UTC"
        now={Date.UTC(2026, 9, 23, 19, 42)}
        min={start === "" ? undefined : start}
        value={end}
        onValueChange={setEnd}
      />
    </div>
  );
}

A time picker is a text input for a time of day, with its reference after it and a clock button at the end. The input reads a time as the crew type it, when they press Enter or leave the field: 14:30, 14.30, 14h30, 1430, 930, 14 for the hour, and on a 12-hour clock 2:30 pm or 2pm. It reads a time with its clock named after it, as the log and messages write one: 14:05 UTC, 1405Z or 14:05 GMT on the UTC clock, 14:05 LT on the local one, and an ISO instant from a log, whose offset turns it into the field's clock (2026-10-23T23:30:00-05:00 is 04:30 UTC). A time named on the other clock is refused with a line that says which clock the field takes. The page receives HH:MM. The field holds a time of day only: a date and a time zone are the caller's, and timeReference prints the reference ("UTC", "ship's time") beside the time, so a time is never shown without one.

The button opens two lists under the field, the hours and the minutes, every 5 minutes unless minuteStep says otherwise. The keys drive them: the up and down arrows move an hour or a minute, Page Up and Page Down move six hours or three steps, Home and End go to the ends, and typed digits go to that hour or minute. The right and left arrows move between the lists. Enter on an hour moves to the minutes, Enter on a minute chooses the time and returns to the button, and Escape or the Close button closes the lists and keeps the time the field had; while they are open, Tab stays in them. Hours and minutes outside min and max are disabled. Under the lists, Now chooses the current time, and a line says which clock it reads: UTC by default, as the log keeps times, or the device's with zone="local".

With a finger the field is the browser's own time field (picker="auto", the default), whose wheels suit a touch screen; with a mouse it is the kit's lists. picker="kit" or picker="native" chooses one everywhere. A typed time the field cannot take keeps its text and names the problem: text it cannot read, or a time outside the bounds. While it does, the field reports no time, so a form never saves the time that stood before the text.

Examples

Filled

UTC
A time already chosen, with its reference beside it.
Code
import { TimePicker } from "@bosunkit/components/time-picker";
import type { ReactElement } from "react";

export const description: string = "A time already chosen, with its reference beside it.";

export default function Filled(): ReactElement {
  return (
    <div style={{ width: "100%", maxWidth: 280 }}>
      <TimePicker label="High water" timeReference="UTC" defaultValue="06:30" />
    </div>
  );
}

Earliest and latest times

UTC
With `min` and `max`, hours and minutes outside them are disabled in the lists, and a typed time past them is named: a lock that works from 06:30 to 18:00.
Code
import { TimePicker } from "@bosunkit/components/time-picker";
import type { ReactElement } from "react";

export const description: string =
  "With `min` and `max`, hours and minutes outside them are disabled in the lists, and a typed time past them is " +
  "named: a lock that works from 06:30 to 18:00.";

export default function Bounds(): ReactElement {
  return (
    <div style={{ width: "100%", maxWidth: 280 }}>
      <TimePicker label="Lock passage" timeReference="UTC" min="06:30" max="18:00" defaultValue="06:30" />
    </div>
  );
}

Minute step

UTC
`minuteStep={15}` lists the quarter hours; a typed time still takes any minute, and its minute joins the list.
Code
import { TimePicker } from "@bosunkit/components/time-picker";
import type { ReactElement } from "react";

export const description: string =
  "`minuteStep={15}` lists the quarter hours; a typed time still takes any minute, and its minute joins the list.";

export default function MinuteStep(): ReactElement {
  return (
    <div style={{ width: "100%", maxWidth: 280 }}>
      <TimePicker label="Engine check" timeReference="UTC" minuteStep={15} defaultValue="09:10" />
    </div>
  );
}

Local time

local time
`zone="local"` makes Now read the device's clock, for a time that belongs to someone ashore; the lists say so.
Code
import { TimePicker } from "@bosunkit/components/time-picker";
import type { ReactElement } from "react";

export const description: string =
  "`zone=\"local\"` makes Now read the device's clock, for a time that belongs to someone ashore; the lists say so.";

export default function LocalZone(): ReactElement {
  return (
    <div style={{ width: "100%", maxWidth: 280 }}>
      <TimePicker label="Taxi booked for" zone="local" timeReference="local time" />
    </div>
  );
}

Invalid

UTC
The caller's own error, shown in place of the field's.
Code
import { TimePicker } from "@bosunkit/components/time-picker";
import type { ReactElement } from "react";

export const description: string = "The caller's own error, shown in place of the field's.";

export default function Invalid(): ReactElement {
  return (
    <div style={{ width: "100%", maxWidth: 280 }}>
      <TimePicker
        label="Relief"
        timeReference="UTC"
        defaultValue="03:00"
        error="The relief must come after the watch starts at 04:00."
      />
    </div>
  );
}

The browser's own field

UTC
`picker="native"` is the browser's own time field everywhere, as `auto` gives it under a finger.
Code
import { TimePicker } from "@bosunkit/components/time-picker";
import type { ReactElement } from "react";

export const description: string =
  "`picker=\"native\"` is the browser's own time field everywhere, as `auto` gives it under a finger.";

export default function Native(): ReactElement {
  return (
    <div style={{ width: "100%", maxWidth: 280 }}>
      <TimePicker label="Arrival" timeReference="UTC" picker="native" defaultValue="12:04" />
    </div>
  );
}

Read-only

UTC
A recorded time shown without letting the crew change it.
Code
import { TimePicker } from "@bosunkit/components/time-picker";
import type { ReactElement } from "react";

export const description: string = "A recorded time shown without letting the crew change it.";

export default function ReadOnly(): ReactElement {
  return (
    <div style={{ width: "100%", maxWidth: 280 }}>
      <TimePicker label="Recorded at" timeReference="UTC" readOnly value="23:45" />
    </div>
  );
}

Props

PropTypeDefaultDescription
labelstring | undefined–The field's name on the outline, translated by the caller; omit it inside a FormField.
valuestring | undefined–The time as HH:MM, "" for none.
defaultValuestring | undefined–The time it starts with when the caller does not hold value.
onValueChange((value: string) => void) | undefined–Called with HH:MM when a time is chosen, or typed and the crew leave the field; "" when it is cleared.
minstring | undefined–The earliest time that can be chosen (HH:MM).
maxstring | undefined–The latest time that can be chosen (HH:MM).
minuteStepnumber | undefined5The minutes the list offers, every 5 unless set; a typed time can take any minute.
zoneClockZone | undefined"utc"The clock that Now reads and the list states: utc by default, as the log keeps times; local for the browser's.
nownumber | undefined–The current instant in epoch milliseconds, in place of the clock's, read on zone's clock.
timeReferencestring | undefined–The time's reference, printed after it in the field, translated by the caller ("UTC", "ship's time").
pickerTimePickerMode | undefined"auto"auto, the default, offers the kit's hour and minute lists with a mouse and the browser's own time wheels with a finger (a coarse pointer); kit and native choose one everywhere.
hintstring | undefined–Text under the field, translated by the caller.
errorstring | undefined–The caller's own error, shown in place of the field's.
requiredboolean | undefined–Whether the field must hold a time.
disabledboolean | undefined–Whether the field takes no input.
readOnlyboolean | undefined–Whether the field shows its time without letting the crew change it.
idstring | undefined–The text input's id, for a Label's htmlFor.
namestring | undefined–The form field name; the form receives HH:MM.
aria-labelstring | undefined–Names the text input when there is no label.
classNamestring | undefined–Classes added to the field's root.
testIdstring | undefined–data-testid on the field.
refRef<HTMLInputElement> | undefined–The text input.
motionMotionProp | undefined–false opens the lists at once and keeps the error line still; true also moves the error line.
  • Time input: the browser's time field in the kit's plain input frame.
  • Date picker: a date chosen from a month calendar, built the same way.
  • Duration input: a length of time rather than a time of day.
  • Schema form: draws its time fields with this picker.