Skip to content

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

InputsReact

Date Field

A date typed in segments, month, day and year in the locale's order, each one a real input.

Rendered by React 19.3.1

Loading React…
import { DateField } from '@hamur/react/date-field'
import { Field } from '@hamur/react/field'

export default function DateFieldExample() {
  return (
    <Field.Root className="field">
      <Field.Label>Due date</Field.Label>
      <DateField.Root className="date-field" locale="en-US" today="2026-11-07">
        <DateField.Input className="date-field-input">
          {({ segments }) =>
            segments.map((segment) => (
              <DateField.Segment
                key={segment.index}
                segment={segment}
                className="date-field-segment"
              />
            ))
          }
        </DateField.Input>
        <DateField.Clear className="date-field-clear">
          ×
        </DateField.Clear>
      </DateField.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:

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

Examples

Birthday

autoComplete="bday" puts the bday-* tokens on the segments, so the browser can fill a saved birthday.

DateFieldBirthday.tsx
import { DateField } from '@hamur/react/date-field'
import { Field } from '@hamur/react/field'

export default function DateFieldBirthdayExample() {
  return (
    <Field.Root className="field">
      <Field.Label>Birthday</Field.Label>
      {/* autoComplete="bday" puts bday-month, bday-day and bday-year on the segments (WCAG 1.3.5). */}
      <DateField.Root className="date-field" locale="en-US" today="2026-11-07" autoComplete="bday">
        <DateField.Input className="date-field-input" />
      </DateField.Root>
    </Field.Root>
  )
}

Limits

min and max in a Field that validates on change: a date outside them shows the Field.Error of match="rangeUnderflow" or match="rangeOverflow", and the rule is the Field.Description.

DateFieldLimits.tsx
import { DateField } from '@hamur/react/date-field'
import { Field } from '@hamur/react/field'

export default function DateFieldLimitsExample() {
  return (
    <Field.Root className="field" validationMode="onChange">
      <Field.Label>Appointment</Field.Label>
      <DateField.Root
        className="date-field"
        locale="en-US"
        today="2026-11-07"
        min="2026-11-03"
        max="2026-12-18"
      >
        <DateField.Input className="date-field-input" />
      </DateField.Root>
      <Field.Description className="muted">From November 3 to December 18, 2026.</Field.Description>
      <Field.Error className="error" match="rangeUnderflow">
        The first free day is November 3.
      </Field.Error>
      <Field.Error className="error" match="rangeOverflow">
        The last free day is December 18.
      </Field.Error>
    </Field.Root>
  )
}

Locale

de-DE (day, month, year with dots) next to ar-EG in right to left with Arabic-Indic digits.

DateFieldLocale.tsx
import { DateField } from '@hamur/react/date-field'
import { Field } from '@hamur/react/field'

export default function DateFieldLocaleExample() {
  return (
    <div className="date-field-locales">
      <Field.Root className="field">
        <Field.Label>German (de-DE)</Field.Label>
        <DateField.Root
          className="date-field"
          locale="de-DE"
          today="2026-11-07"
          defaultValue="2026-11-12"
        >
          <DateField.Input className="date-field-input" />
        </DateField.Root>
      </Field.Root>
      <Field.Root className="field">
        <Field.Label>Arabic, Egypt (ar-EG)</Field.Label>
        <DateField.Root
          className="date-field"
          dir="rtl"
          locale="ar-EG"
          today="2026-11-07"
          defaultValue="2026-11-12"
        >
          <DateField.Input className="date-field-input" />
        </DateField.Root>
      </Field.Root>
    </div>
  )
}

Form

Inside Field and Form with required: an empty or partial date blocks the submit with its message, and a full one submits as due=2026-11-12.

DateFieldForm.tsx
import * as React from 'react'
import { DateField } from '@hamur/react/date-field'
import { Field } from '@hamur/react/field'
import { Form } from '@hamur/react/form'

export default function DateFieldFormExample() {
  const [submitted, setSubmitted] = React.useState('')
  return (
    <Form
      className="stack"
      onFormSubmit={(_values, details) => {
        const form = details.event.target as HTMLFormElement
        setSubmitted([...new FormData(form)].map(([key, value]) => `${key}=${value}`).join('&'))
      }}
    >
      <Field.Root name="due" className="field">
        <Field.Label>Due date</Field.Label>
        <DateField.Root className="date-field" locale="en-US" today="2026-11-07" required>
          <DateField.Input className="date-field-input" />
        </DateField.Root>
        <Field.Error className="error" />
      </Field.Root>
      <button className="button" type="submit">
        Save
      </button>
      {submitted ? <pre className="output">{submitted}</pre> : null}
    </Form>
  )
}

Auto Advance Off

autoAdvance={false}: a full segment keeps focus until Tab or ArrowRight, and the text next to the field says so.

DateFieldAutoAdvanceOff.tsx
import { DateField } from '@hamur/react/date-field'
import { Field } from '@hamur/react/field'

export default function DateFieldAutoAdvanceOffExample() {
  return (
    <Field.Root className="field">
      <Field.Label>Start date</Field.Label>
      <DateField.Root className="date-field" locale="en-US" today="2026-11-07" autoAdvance={false}>
        <DateField.Input className="date-field-input" />
      </DateField.Root>
      <Field.Description className="muted">
        Focus stays on a part when it is full. Press Tab or the right arrow to move on.
      </Field.Description>
    </Field.Root>
  )
}

API reference

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

ClearDateField.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>.

InputDateField.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)—

RootDateField.Root

Groups all parts of a field that edits a date in segments (month, day, year in the locale's order). Values are ISO 8601 strings such as '2026-11-07'. onValueChange is called only with a complete, real date, or null. Renders a <div>.

Root props
PropTypeDefault
valueThe date, null for none.`${number}-${number}-${number}` | null—
defaultValueThe date when initially rendered.`${number}-${number}-${number}` | null—
onValueChangeEvent handler called when the date changes: a complete, real date, or null when a complete((value: `${number}-${number}-${number}` | null, eventDetails: DateFieldRootChangeEventDetails) => void)—
minThe earliest valid date ('2026-01-01').`${number}-${number}-${number}`—
maxThe latest valid date.`${number}-${number}-${number}`—
isDateUnavailableWhether a date cannot be chosen.((date: `${number}-${number}-${number}`) => boolean)—
stepThe native step of the date in days.number—
autoComplete'bday' gives the segments the bday-day, bday-month and bday-year autocomplete tokens."off" | "bday"'off'
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>—
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.

SegmentDateField.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; Arabic-Indic and full-width digits work tooSegment
In the month, pick a month by its name; other segments ignore themSegment
ArrowUpArrowDownStep the segment by one and wrap, without carrying into the next; an empty one starts from placeholderValueSegment
PageUpPageDownStep the day by 7, the month by 3 and the year by 10Segment
HomeEndSet the segment to its smallest or largest numberSegment
ArrowLeftArrowRightMove to the previous or next segmentSegment
ArrowLeftArrowRightSwapped: ArrowLeft moves to the next segmentSegment
BackspaceDeletes the last digit; on an empty segment, moves to the previous oneSegment
DeleteClears the segmentSegment
TabMoves to the next segment, then out of the field: every segment is a tab stopSegment
EnterEscapeNot captured: Enter submits the form as in any text input, Escape is left to a dialog around the fieldSegment
ControlMetaAltUntouched: copy, undo, select all 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 datetabbing in reads the name and the whole value once, "Due date, Thursday, November 12, 2026"
Segmentrole="spinbutton", aria-label the segment's name, aria-valuemin, aria-valuemax, aria-valuenow when filled, aria-valuetext "Empty" when noteach part is read as "month, Due date, 11", and an empty one says so
Segmentaria-invalid and aria-errormessage when the value is invalid, aria-describedby the description on the first segmentthe error is on the element that has focus, not on the group (where aria-invalid is not allowed)
Segmenta visible placeholder (mm, dd, yyyy) and the bday-* autocomplete tokens with autoComplete="bday"an empty segment shows what goes there, and the browser can fill a birthday (WCAG 1.3.5)
Inputnothing: no roleit only lays out the segments; literals inside it are aria-hidden
Cleararia-label "Clear" (from labels), tabindex="-1"the button is named and adds no tab stop; Backspace clears from the keyboard

Focus order

  1. Each editable segment is a tab stop, in the locale's order; literals are never focused. Nothing takes focus on mount.
  2. Typing a full segment moves focus to the next one unless autoAdvance is false; ArrowLeft and ArrowRight move between segments, swapped in right to left.
  3. Backspace on an empty segment moves to the previous one.
  4. Clear is out of the tab order. After the last segment, Tab leaves the field.