Skip to content
txbrendan/ui

Time Field

beta

A 24-hour time, typed or picked from a list under the field. "19", "1930", "19.30", and "7pm" all settle as 19:00-style times, and an end time shows how long it makes the event.

Preview

start and end

Try 7pm or 1930.

1 h 30 min

The list starts after the start.

size

states

Every 30 minutes, 17:00 to 21:00.

Install

  1. 1. Add the registry to components.json, once per app. Without it, the shadcn CLI looks up @txbrendan in a public list that txbrendan/ui does not control.

    npx shadcn@latest registry add "@txbrendan=https://txbrendan.dev/r/{name}.json"
  2. 2. Add the theme, once per app. It replaces your color tokens with ones checked for contrast.

    npx shadcn@latest add @txbrendan/theme
  3. 3. Add the component.

    npx shadcn@latest add @txbrendan/time-field

Use it for

  • A start or end time for a meeting, a session, or a booking. Pair it with a DatePicker for a date and a time.
  • Give it a label, like "Starts". Without one, pass aria-label.
  • The value is "HH:MM" on a 24-hour clock, or "" for none. onValueChange only ever gets a good time.
  • Typed text settles when the field is left or Enter is pressed. Text it cannot read says so in red, and the last good time stays.
  • For an end time, pass the start as from. The list begins after it, each time says how long that makes it, and the field shows the length.
  • step sets the minutes between listed times. Any minute can still be typed. min and max default to 06:00 and 23:00.

Avoid it for

  • A length of time, like 90 minutes. Use a NumberStepper with a duration UnitSwitch.
  • A date. Use a DatePicker.
  • Times that cross midnight. The value has no date, so 23:30 to 00:30 cannot be told from a backwards range.

Props

  • label

    Names the field, above it.

    ReactNode

  • value

    The time, "HH:MM", or "" for none. Use it with onValueChange.

    string

  • defaultValue

    The time at first render, when you do not control it.

    string · default ""

  • onValueChange

    Called with a good "HH:MM" once the field settles, or "" when cleared.

    (time: string) => void

  • from

    The start, for an end time. The list begins after it and says each length.

    string

  • step

    Minutes between listed times.

    number · default 15

  • min

    The earliest time allowed.

    string · default "06:00"

  • max

    The latest time allowed.

    string · default "23:00"

  • placeholder

    Shown while the field is empty.

    string · default "--:--"

  • hint

    A short note under the field.

    ReactNode

  • error

    Replaces the hint, in red. The field's own reading problems show first.

    ReactNode

  • size

    Height and text size of the input, the label, and the list.

    "sm" | "md" | "lg" · default "md"

  • disabled

    Blocks typing and the list.

    boolean · default false

  • defaultOpen

    Opens the list on first render, for docs and screenshots.

    boolean · default false

  • name

    Form field name. Sends the settled time.

    string

  • className

    Classes for the frame. Later classes win.

    string

Accessibility

  • The input is a combobox. It says it has a list, and whether the list is open.
  • Focus stays in the input. The highlighted time is its active descendant, so screen readers read it as the keys move.
  • Typing, a click, or the down arrow opens the list at the nearest time. The arrows move a step, and Page Up, Page Down, or Shift with an arrow move an hour.
  • Enter takes the highlighted time once the keys have moved it. Otherwise it takes the typed one. Home and End stay in the text.
  • The first Esc closes the list, and the next puts back the last good time. With nothing to undo, Esc goes on to the page.
  • A problem reading the time is shown in red under the field and announced once. The input is marked invalid.
  • For an end time, the length after the time describes the input.

Examples

A start and an end

const [start, setStart] = useState("19:00");
const [end, setEnd] = useState("20:30");

<TimeField label="Starts" value={start} onValueChange={setStart} />
<TimeField label="Ends" value={end} onValueChange={setEnd} from={start} />

Every 30 minutes, in the evening

<TimeField label="Workshop" step={30} min="17:00" max="21:00" />