|Kit

Time Input

HOUR/MINUTE (+ SECOND) + AM/PM time entry composed from NumberInput + SegmentGroup — Ark ships no time-picker machine, so this is the kit's own composed time field.

TimeInput is a standalone time entry — HOUR / MINUTE (+ SECOND) steppers next to an AM/PM toggle, composed from NumberInput and SegmentGroup. Ark UI has no time-picker machine (only a countdown timer), so this is hand-composed from the kit's own primitives rather than a wrapped Ark part — it's the same time row DatePicker will embed in its popover once it lands.

import { TimeInput } from '@42/ui-react/time-input';

<TimeInput label="Meeting time" defaultValue={{ hour: 9, minute: 30 }} />

The value is { hour, minute, second? } | nullhour is always stored in 24h, regardless of what's displayed. There's no built-in range mode: a "start" / "end" pair reads better as two labelled TimeInputs than as one control split down the middle with nothing to tell its two halves apart — see Start / end pairs.

Playground

Component

SpinnerLoading preview

Props

Code

<Component  label="Meeting time"  minuteStep={1}  placeholder="--"/>

12h vs 24h

Display defaults to the active locale's own convention (Intl.DateTimeFormat's hourCycle) — the AM/PM segment group only renders in a 12h locale. Override it with format (12 | 24) or hourCycle for the exact Unicode cycle ("h11" | "h12" | "h23" | "h24"). The stored hour is always 24h either way — only the display and the AM/PM segment change.

// Force 24h regardless of locale
<TimeInput format={24} defaultValue={{ hour: 14, minute: 5 }} />

// Force 12h regardless of locale
<TimeInput format={12} defaultValue={{ hour: 14, minute: 5 }} />

Start / end pairs

There's no range prop. Two TimeInputs, each with its own label, reads more clearly than one control with an unlabelled start half and end half — compose them yourself:

Code

<div className="flex gap-4">  <TimeInput label="Start time" defaultValue={{ hour: 9, minute: 0 }} />  <TimeInput label="End time" defaultValue={{ hour: 17, minute: 30 }} /></div>

Seconds & minute step

withSeconds adds a third stepper; minuteStep sets the minute increment (e.g. 15 for quarter-hour scheduling). Every stepper wraps at its bounds (23 → 0, 59 → 0, …) instead of clamping.

Sizes

Five sizes — xs through xl — matching the rest of the family.

Code

<TimeInput size="xs" defaultValue={{ hour: 9, minute: 30 }} /><TimeInput size="sm" defaultValue={{ hour: 9, minute: 30 }} /><TimeInput size="md" defaultValue={{ hour: 9, minute: 30 }} /><TimeInput size="lg" defaultValue={{ hour: 9, minute: 30 }} /><TimeInput size="xl" defaultValue={{ hour: 9, minute: 30 }} />

Label, description & error

label / description / error / required come from the built-in Field wrapper, same as the rest of the family.

Local time for all attendees
Pick a time

Code

<TimeInput  label="Meeting time"  description="Local time for all attendees"  defaultValue={{ hour: 9, minute: 30 }}  required/><TimeInput  label="Meeting time"  error="Pick a time"  defaultValue={{ hour: 0, minute: 0 }}/>

Controlled

Drive the value from state. onChange hands you the next { hour, minute, second? }.

'use client';
import { useState } from 'react';
import { TimeInput, type TimeValue } from '@42/ui-react/time-input';

export function MeetingTime() {
  const [value, setValue] = useState<TimeValue>({ hour: 9, minute: 30 });
  return <TimeInput label="Meeting time" value={value} onChange={setValue} />;
}

Forms

TimeInput has no machine-backed hidden part to piggyback on (unlike Select's hidden <select>), so name renders a hand-rolled hidden HH:mm[:ss] input instead — a <form> submission and FormData pick it up the same way.

<form action={save}>
  <TimeInput name="startTime" defaultValue={{ hour: 9, minute: 0 }} />
</form>

API

Prop

Type

Accessibility

  • HOUR / MINUTE / SECOND are real role="spinbutton" NumberInputs, each with its own accessible name (Hour / Minute / Second) and the full stepper keyboard model ( / , PageUp / PageDown, Home / End). The name stays wired even though the label isn't shown — it's visually hidden (sr-only), not removed, so screen readers still announce it. The h / m / s suffix next to each value is decorative only (aria-hidden), not a replacement accessible name.
  • The AM/PM toggle is a labeled SegmentGroup (a radio-group under the hood) — arrow keys move between the two segments. Its "Period" label is likewise visually hidden but still the accessible name; the segments' own "AM"/"PM" text is visible and self-descriptive.
  • Every stepper wraps at its bounds rather than clamping, so keyboard users can cycle past midnight/the hour without hitting a dead end.
  • invalid / error propagate to every sub-control's aria-invalid; label / description / error associate through the built-in Field.

On this page