Skip to content

Early previewhamur is not on npm yet. The components are being built in the open, follow along on GitHub.

InputsReact

Time Field

A time of day typed in segments: hour, minute, optional seconds and, in a 12-hour cycle, a day period (AM or PM).

Rendered by React 19.3.1

Loading React…
import { TimeField } from '@hamur/react/time-field'
import { Field } from '@hamur/react/field'

export default function TimeFieldExample() {
  return (
    <Field.Root className="field">
      <Field.Label>Start time</Field.Label>
      <TimeField.Root
        className="date-field"
        locale="en-US"
        today="2026-11-07"
        hourCycle={12}
      >
        <TimeField.Input className="date-field-input">
          {({ segments }) =>
            segments.map((segment) => (
              <TimeField.Segment
                key={segment.index}
                segment={segment}
                className="date-field-segment"
              />
            ))
          }
        </TimeField.Input>
        <TimeField.Clear className="date-field-clear">
          ×
        </TimeField.Clear>
      </TimeField.Root>
    </Field.Root>
  )
}
Not measured yetTests passing in React
Not measured yetSize
Not measured yetMemory
Not measured yetSpeed
[N] commits

Installation

npm i @hamur/react

Not on npm yet. The package goes out under the chosen name.

Anatomy

Import the component and assemble its parts:

<TimeField.Root>
  <TimeField.Input>
    <TimeField.Segment />
  </TimeField.Input>
  <TimeField.Clear />
</TimeField.Root>

Examples

24h Seconds

hourCycle={24} and granularity="second": hour, minute and second, and the value beneath reads 14:30:15.

TimeField24hSeconds.tsx
import * as React from 'react'
import { TimeField } from '@hamur/react/time-field'
import { Field } from '@hamur/react/field'

export default function TimeField24hSecondsExample() {
  const [value, setValue] = React.useState<string | null>(null)
  return (
    <Field.Root className="field">
      <Field.Label>Lap time</Field.Label>
      <TimeField.Root
        className="date-field"
        locale="en-US"
        today="2026-11-07"
        hourCycle={24}
        granularity="second"
        onValueChange={(next) => setValue(next)}
      >
        <TimeField.Input className="date-field-input" />
      </TimeField.Root>
      <p className="muted">Value: {value ?? 'none'}</p>
    </Field.Root>
  )
}

API reference

26 props across 4 parts, in React syntax. className, style, render are listed once, under Shared props.

ClearTimeField.Clear

Clears the whole value, including a partly typed one. It is out of the tab order: Backspace clears a segment from the keyboard. Renders a <button>.

InputTimeField.Input

The visual control: it wraps the segments. Its children are a function of the segments, since their order depends on the locale; without children it renders every segment. Renders a <div>.

Input props
PropTypeDefault
childrenThe segments, or a function of them: ({ segments }) => ....React.ReactNode | ((context: { segments: FieldSegment[]; }) => React.ReactNode)—

RootTimeField.Root

Groups all parts of a field that edits a time of day in segments (hour, minute, optional seconds, day period). Values are strings such as '14:30', '14:30:15' or '14:30:15.250'; every HTML form is accepted and one canonical form is reported. Renders a <div>.

Root props
PropTypeDefault
onIncompleteChangeEvent handler called when the segments change while no complete value exists, with the((parts: FieldParts, eventDetails: DateFieldRootChangeEventDetails) => void)—
localeThe BCP 47 locale of the segment order, literals, digits and day periods.string'en-US'
forceLeadingZerosWhether the day, month and hour show a leading zero.boolean—
placeholderValueThe value an empty segment takes on its first Up or Down press.string—
todayThe date of today ('2026-11-07'), for the starting point of an empty date segment.`${number}-${number}-${number}`—
timeZoneThe time zone that decides what today is, when today is not given.string—
autoAdvanceWhether focus moves to the next segment when the typed number cannot get another digit.booleantrue
segmentRoleThe role of the segments.DateFieldSegmentRole'spinbutton'
labelsThe texts the field adds: the segment names, the text of an empty segment, the Clear label.Partial<DateFieldLabels>—
disabledWhether the component should ignore user interaction.booleanfalse
readOnlyWhether the user should be unable to change the value.booleanfalse
requiredWhether the user must fill in the field before submitting a form.booleanfalse
invalidWhether the field is invalid.booleanfalse
nameIdentifies the field when a form is submitted.string—
formIdentifies the form that owns the hidden input.string—
inputRefA ref to access the first segment.React.Ref<HTMLInputElement>—
valueThe value, null for none.TimeString | null—
defaultValueThe value when initially rendered.TimeString | null—
onValueChangeEvent handler called when the value changes: a complete value, or null when a complete((value: TimeString | null, eventDetails: DateFieldRootChangeEventDetails) => void)—
minThe earliest valid value.TimeString—
maxThe latest valid value.TimeString—
hourCycleThe hour cycle, 12 (with a day period segment) or 24.12 | 24—
granularityThe finest segment shown.TimeGranularity—
stepThe native step in seconds.number—
Root data attributes
Data attributeDescription
data-disabledPresent when the field is disabled.
data-readonlyPresent when the field does not allow changing the value.
data-requiredPresent when the field is required.
data-emptyPresent when no segment has a value.
data-filledPresent when every segment has a value.
data-incompletePresent when some segments have a value and others do not.
data-focusedPresent when focus is inside the field.
data-invalidPresent when the field is invalid.

SegmentTimeField.Segment

One segment of the value. An editable segment (year, month, day, hour, minute, second, millisecond, day period) is a real <input> with the spinbutton role; a literal (/, :) is a <span> hidden from assistive technology., or a <span> for a literal. Renders an <input>.

Segment props
PropTypeDefault
segmentrequiredThe segment to render, one of the segments the Input gives its children.FieldSegment—

Shared props

Every part that renders an element takes these.

Shared props
PropType
classNameCSS class applied to the element, or a function that returns a class based on the component's state.string | ((state: State) => string | undefined)
styleStyle applied to the element, or a function that returns a style object based on the component's state.React.CSSProperties | ((state: State) => React.CSSProperties | undefined)
renderAllows you to replace the component's HTML element with a different tag, or compose it with another component.React.ReactElement<unknown, string | React.JSXElementConstructor<any>> | ComponentRenderFn<HTMLProps, State>

Keyboard and accessibility

Keyboard

Keyboard
KeysDoesPart
Types into the segment and moves on when it is full: in a 12-hour cycle the hour 1 waits for 10 to 12Segment
APOn the day period, set AM or PM; other letters are ignoredSegment
ArrowUpArrowDownStep the segment by one, or the minute and second by step when it divides evenly; on the day period, toggleSegment
PageUpPageDownStep the hour by 6 and the minute and second by 15Segment
HomeEndSet the segment to its smallest or largest numberSegment
ArrowLeftArrowRightMove to the previous or next segment, swapped in right to leftSegment
BackspaceDeletes the last digit; on an empty segment, moves to the previous oneSegment
DeleteClears the segmentSegment
EnterEscapeNot captured: Enter submits the form, Escape is left to a dialog around the fieldSegment
ControlMetaAltUntouched: copy, undo and the browser's shortcuts workSegment

Accessibility

What the parts set
PartSetsSo that
Rootrole="group", aria-labelledby the Field label and a visually hidden text of the full timetabbing in reads "Start time, 2:30 PM" once
Segmentrole="spinbutton", aria-label the segment's name, aria-valuenow when filled, aria-valuetext for a 12-hour hour with its period, and "Empty"the hour is read "2 PM", not "2", and an empty segment says so
Segmentinputmode="numeric" on the numbers and inputmode="text" on the day perioda phone shows digits for the numbers and letters for AM and PM
Segmentaria-invalid and aria-errormessage when the time is invalidthe error is on the element that has focus
Cleararia-label "Clear" (from labels), tabindex="-1"the button is named and adds no tab stop

Focus order

  1. Each editable segment is a tab stop: hour, minute, second, then the day period in en-US.
  2. A full segment moves focus on unless autoAdvance is false.
  3. Clear is out of the tab order.