Skip to content

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

InputsReact

Date Picker

A DateField with a calendar popup: the field is the primary input, a trigger button opens a dialog that holds a Calendar, and both edit one value, an ISO 8601 string such as '2026-11-12'.

Rendered by React 19.3.1

Loading React…
import { Calendar } from '@hamur/react/calendar'
import { DatePicker } from '@hamur/react/date-picker'
import { Field } from '@hamur/react/field'

export default function DatePickerExample() {
  return (
    <Field.Root className="field">
      <Field.Label>Due date</Field.Label>
      <DatePicker.Root locale="en-US" today="2026-11-07">
        <DatePicker.Control className="date-field date-picker-control">
          <DatePicker.Input className="date-field-input">
            {({ segments }) =>
              segments.map((segment) => (
                <DatePicker.Segment
                  key={segment.index}
                  segment={segment}
                  className="date-field-segment"
                />
              ))
            }
          </DatePicker.Input>
          <DatePicker.Clear className="date-field-clear">
            ×
          </DatePicker.Clear>
          <DatePicker.Trigger className="date-picker-trigger">
            <svg width="16" height="16" viewBox="0 0 16 16" aria-hidden="true">
              <path d="M2.5 3.5h11v10h-11zM2.5 6.5h11M5.5 1.5v3M10.5 1.5v3" />
            </svg>
          </DatePicker.Trigger>
        </DatePicker.Control>
        <DatePicker.Portal>
          <DatePicker.Positioner sideOffset={8}>
            <DatePicker.Popup className="popover date-picker-popup">
              <DatePicker.Calendar className="calendar">
                <div className="calendar-header">
                  <Calendar.PrevButton className="calendar-nav">‹</Calendar.PrevButton>
                  <Calendar.Heading className="calendar-heading" />
                  <Calendar.NextButton className="calendar-nav">›</Calendar.NextButton>
                </div>
                <Calendar.Grid className="calendar-grid">
                  {(grid) => (
                    <>
                      <Calendar.GridHeader>
                        <Calendar.GridRow>
                          {grid.weekdays.map((weekday) => (
                            <Calendar.HeaderCell
                              key={weekday.index}
                              weekday={weekday.index}
                              className="calendar-weekday"
                            />
                          ))}
                        </Calendar.GridRow>
                      </Calendar.GridHeader>
                      <Calendar.GridBody>
                        {grid.weeks.map((week, index) => (
                          <Calendar.GridRow key={index}>
                            {week.map((day) => (
                              <Calendar.Cell
                                key={day.date}
                                date={day.date}
                                className="calendar-cell"
                              >
                                <Calendar.Day className="calendar-day" />
                              </Calendar.Cell>
                            ))}
                          </Calendar.GridRow>
                        ))}
                      </Calendar.GridBody>
                    </>
                  )}
                </Calendar.Grid>
              </DatePicker.Calendar>
              <DatePicker.Close className="button date-picker-close">
                Close
              </DatePicker.Close>
            </DatePicker.Popup>
          </DatePicker.Positioner>
        </DatePicker.Portal>
      </DatePicker.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:

<DatePicker.Root>
  <DatePicker.Control>
    <DatePicker.Input>
      <DatePicker.Segment />
    </DatePicker.Input>
    <DatePicker.Clear />
    <DatePicker.Trigger />
  </DatePicker.Control>
  <DatePicker.Portal>
    <DatePicker.Positioner>
      <DatePicker.Popup>
        <DatePicker.Calendar />
        <DatePicker.Close />
      </DatePicker.Popup>
    </DatePicker.Positioner>
  </DatePicker.Portal>
</DatePicker.Root>

// Not in this example: DatePicker.Preset

Examples

Limits

min and max with isDateUnavailable for weekends, in a Field: the calendar disables them and a typed one is invalid.

DatePickerLimits.tsx
import { Calendar } from '@hamur/react/calendar'
import { DatePicker } from '@hamur/react/date-picker'
import { Field } from '@hamur/react/field'

// Saturday and Sunday are booked: the calendar disables them and a typed one is invalid.
const isWeekend = (date: string) => {
  const day = new Date(`${date}T00:00:00Z`).getUTCDay()
  return day === 0 || day === 6
}

export default function DatePickerLimitsExample() {
  return (
    <Field.Root className="field" validationMode="onChange">
      <Field.Label>Appointment</Field.Label>
      <DatePicker.Root
        locale="en-US"
        today="2026-11-07"
        min="2026-11-03"
        max="2026-12-18"
        isDateUnavailable={isWeekend}
      >
        <DatePicker.Control className="date-field date-picker-control">
          <DatePicker.Input className="date-field-input" />
          <DatePicker.Trigger className="date-picker-trigger">
            <svg width="16" height="16" viewBox="0 0 16 16" aria-hidden="true">
              <path d="M2.5 3.5h11v10h-11zM2.5 6.5h11M5.5 1.5v3M10.5 1.5v3" />
            </svg>
          </DatePicker.Trigger>
        </DatePicker.Control>
        <DatePicker.Portal>
          <DatePicker.Positioner sideOffset={8}>
            <DatePicker.Popup className="popover date-picker-popup">
              <DatePicker.Calendar className="calendar">
                <div className="calendar-header">
                  <Calendar.PrevButton className="calendar-nav">‹</Calendar.PrevButton>
                  <Calendar.Heading className="calendar-heading" />
                  <Calendar.NextButton className="calendar-nav">›</Calendar.NextButton>
                </div>
                <Calendar.Grid className="calendar-grid">
                  {(grid) => (
                    <>
                      <Calendar.GridHeader>
                        <Calendar.GridRow>
                          {grid.weekdays.map((weekday) => (
                            <Calendar.HeaderCell
                              key={weekday.index}
                              weekday={weekday.index}
                              className="calendar-weekday"
                            />
                          ))}
                        </Calendar.GridRow>
                      </Calendar.GridHeader>
                      <Calendar.GridBody>
                        {grid.weeks.map((week, index) => (
                          <Calendar.GridRow key={index}>
                            {week.map((day) => (
                              <Calendar.Cell
                                key={day.date}
                                date={day.date}
                                className="calendar-cell"
                              >
                                <Calendar.Day className="calendar-day" />
                              </Calendar.Cell>
                            ))}
                          </Calendar.GridRow>
                        ))}
                      </Calendar.GridBody>
                    </>
                  )}
                </Calendar.Grid>
              </DatePicker.Calendar>
            </DatePicker.Popup>
          </DatePicker.Positioner>
        </DatePicker.Portal>
      </DatePicker.Root>
      <Field.Description className="muted">
        Weekdays from November 3 to December 18, 2026. Weekends are booked.
      </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>
  )
}

Form

Inside Field and Form with required and a name: the date submits as due=2026-11-12.

DatePickerForm.tsx
import * as React from 'react'
import { Calendar } from '@hamur/react/calendar'
import { DatePicker } from '@hamur/react/date-picker'
import { Field } from '@hamur/react/field'
import { Form } from '@hamur/react/form'

export default function DatePickerFormExample() {
  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>
        <DatePicker.Root locale="en-US" today="2026-11-07" required>
          <DatePicker.Control className="date-field date-picker-control">
            <DatePicker.Input className="date-field-input" />
            <DatePicker.Trigger className="date-picker-trigger">
              <svg width="16" height="16" viewBox="0 0 16 16" aria-hidden="true">
                <path d="M2.5 3.5h11v10h-11zM2.5 6.5h11M5.5 1.5v3M10.5 1.5v3" />
              </svg>
            </DatePicker.Trigger>
          </DatePicker.Control>
          <DatePicker.Portal>
            <DatePicker.Positioner sideOffset={8}>
              <DatePicker.Popup className="popover date-picker-popup">
                <DatePicker.Calendar className="calendar">
                  <div className="calendar-header">
                    <Calendar.PrevButton className="calendar-nav">‹</Calendar.PrevButton>
                    <Calendar.Heading className="calendar-heading" />
                    <Calendar.NextButton className="calendar-nav">›</Calendar.NextButton>
                  </div>
                  <Calendar.Grid className="calendar-grid">
                    {(grid) => (
                      <>
                        <Calendar.GridHeader>
                          <Calendar.GridRow>
                            {grid.weekdays.map((weekday) => (
                              <Calendar.HeaderCell
                                key={weekday.index}
                                weekday={weekday.index}
                                className="calendar-weekday"
                              />
                            ))}
                          </Calendar.GridRow>
                        </Calendar.GridHeader>
                        <Calendar.GridBody>
                          {grid.weeks.map((week, index) => (
                            <Calendar.GridRow key={index}>
                              {week.map((day) => (
                                <Calendar.Cell
                                  key={day.date}
                                  date={day.date}
                                  className="calendar-cell"
                                >
                                  <Calendar.Day className="calendar-day" />
                                </Calendar.Cell>
                              ))}
                            </Calendar.GridRow>
                          ))}
                        </Calendar.GridBody>
                      </>
                    )}
                  </Calendar.Grid>
                </DatePicker.Calendar>
              </DatePicker.Popup>
            </DatePicker.Positioner>
          </DatePicker.Portal>
        </DatePicker.Root>
        <Field.Error className="error" />
      </Field.Root>
      <button className="button" type="submit">
        Save
      </button>
      {submitted ? <pre className="output">{submitted}</pre> : null}
    </Form>
  )
}

API reference

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

CalendarDatePicker.Calendar

The calendar of the popup: Calendar.Root (or RangeCalendar.Root) wired to the picker's value, limits, locale and visible month. Compose it with the Calendar parts (Calendar.PrevButton, Calendar.Heading, Calendar.Grid and the rest). It takes no part in the Field: the control already submits and validates the value. Renders a <div>.

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

CloseDatePicker.Close

A button that closes the popover. Renders a <button>.

Close props
PropTypeDefault
nativeButtonWhether the component renders a native <button> element when replacing itbooleantrue

ControlDatePicker.Control

The group that holds the field and the trigger, and the element the popup is anchored to. It is the field's root: it is named by the Field label, shows the Field's validity, and counts the popup as inside it, so moving focus into the calendar neither touches nor validates the field. Renders a <div>.

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

PopupDatePicker.Popup

The dialog that holds the calendar and the presets. It is named "Choose date" unless you name it. Focus moves to the highlighted day when it opens, and returns to the trigger (or to the segment Alt+Down was pressed on) when it closes. Renders a <div>.

Popup props
PropTypeDefault
initialFocusDetermines the element to focus when the popover is opened.boolean | React.RefObject<HTMLElement | null> | ((openType: InteractionType) => boolean | void | HTMLElement | null)—
finalFocusDetermines the element to focus when the popover is closed.boolean | React.RefObject<HTMLElement | null> | ((closeType: InteractionType) => boolean | void | HTMLElement | null)—

PortalDatePicker.Portal

A portal element that moves the popup to a different part of the DOM. By default, the portal element is appended to <body>. Renders a <div>.

Portal props
PropTypeDefault
keepMountedWhether to keep the portal mounted in the DOM while the popup is hidden.booleanfalse
containerA parent element to render the portal element into.HTMLElement | ShadowRoot | React.RefObject<HTMLElement | ShadowRoot | null> | null—

PositionerDatePicker.Positioner

Positions the popup against the control (the field and the trigger together). Renders a <div>.

Positioner props
PropTypeDefault
alignHow to align the popup relative to the specified side.Align'start'
anchorAn element to position the popup against.Element | VirtualElement | React.RefObject<Element | null> | (() => Element | VirtualElement | null) | null—
positionMethodDetermines which CSS position property to use."absolute" | "fixed"'absolute'
sideWhich side of the anchor element to align the popup against.Side'bottom'
sideOffsetDistance between the anchor and the popup in pixels.number | OffsetFunction0
alignOffsetAdditional offset along the alignment axis in pixels.number | OffsetFunction0
collisionBoundaryAn element or a rectangle that delimits the area that the popup is confined to.Boundary'clipping-ancestors'
collisionPaddingAdditional space to maintain from the edge of the collision boundary.Padding5
stickyWhether to maintain the popup in the viewport afterbooleanfalse
arrowPaddingMinimum distance to maintain between the arrow and the edges of the popup.number5
disableAnchorTrackingWhether to disable the popup from tracking any layout shift of its positioning anchor.booleanfalse
collisionAvoidanceDetermines how to handle collisions when positioning the popup.CollisionAvoidance—

PresetDatePicker.Preset

A button that sets the value to a date or a range, such as "Last 7 days". The value can be a function of today, which the picker reads from the clock after mount (or from today). It closes the popup like a day does. Renders a <button>.

Preset props
PropTypeDefault
valuerequiredThe value the preset sets: a date ('2026-11-07'), a range ({ start, end }), or a function`${number}-${number}-${number}` | DateRange | ((today: `${number}-${number}-${number}`) => `${number}-${number}-${number}` | DateRange)—
nativeButtonWhether the component renders a native <button> element when replacing itbooleantrue

RootDatePicker.Root

Groups all parts of a field with a calendar popup that picks one date. It renders no element. Values are ISO 8601 strings such as '2026-11-07'; onValueChange is called only with a complete, real date, or null.

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.((value: `${number}-${number}-${number}` | null, eventDetails: DatePickerRootChangeEventDetails) => void)—
autoComplete'bday' gives the segments the bday-day, bday-month and bday-year autocomplete tokens."off" | "bday"'off'
openWhether the popup is open.boolean—
defaultOpenWhether the popup is initially open.booleanfalse
onOpenChangeEvent handler called when the popup opens or closes.((open: boolean, eventDetails: DatePickerRootOpenChangeEventDetails) => void)—
onOpenChangeCompleteEvent handler called after any animations complete when the popup opens or closes.((open: boolean) => void)—
actionsRefA ref to imperative actions: unmount and close.React.RefObject<PopoverRootActions | null>—
modalWhether the rest of the page is inert while the popup is open.boolean | "trap-focus"false
closeOnSelectWhether choosing a day (the second end of a range) or a preset closes the popup.booleantrue
minThe earliest date that can be chosen.`${number}-${number}-${number}`—
maxThe latest date that can be chosen.`${number}-${number}-${number}`—
isDateUnavailableWhether a date cannot be chosen but can hold focus.((date: `${number}-${number}-${number}`, anchor: `${number}-${number}-${number}` | null) => boolean)—
isDateDisabledWhether a date is skipped by navigation and cannot be chosen.((date: `${number}-${number}-${number}`) => boolean)—
localeThe BCP 47 locale of the field and the calendar.string'en-US'
firstDayOfWeekThe first day of the week, 0 (Sunday) to 6.number—
visibleMonthThe first visible month.`${number}-${number}-${number}` | `${number}-${number}` | null—
onVisibleMonthChangeEvent handler called when the visible month changes.((month: `${number}-${number}`, eventDetails: DatePickerVisibleMonthChangeEventDetails) => void)—
visibleMonthsHow many months are visible.number1
pageBehaviorWhether Previous and Next move by the visible months or by one."visible" | "single"'visible'
fixedWeeksWhether every month shows six weeks.booleanfalse
todayThe date of today, for the calendar and the presets.`${number}-${number}-${number}`—
timeZoneThe time zone that decides what today is, when today is not given.string—
labelsThe texts the picker, the field and the calendar add (the trigger and popup names, the ClosePartial<DatePickerLabels & DateFieldLabels & CalendarLabels>—
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—
autoAdvanceWhether focus moves to the next segment when the typed number cannot get another digit.booleantrue
segmentRoleThe role of the segments.DateFieldSegmentRole'spinbutton'
onIncompleteChangeEvent handler called when the segments change while no complete value exists.((parts: FieldParts, eventDetails: any) => void)—
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 inputs.string—
inputRefA ref to access the first segment.React.Ref<HTMLInputElement>—
childrenReact.ReactNode—

SegmentDatePicker.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—

TriggerDatePicker.Trigger

A button that opens the popup. It is named "Choose date", or "Change date, Saturday, November 7, 2026" once a date is chosen. It sits inside the control. Renders a <button>.

Trigger props
PropTypeDefault
nativeButtonWhether the component renders a native <button> element when replacing itbooleantrue
idID of the trigger.string—

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
EnterSpaceOn the trigger: opens the popup and moves focus to the highlighted dayTrigger
AltArrowDownOn a segment: opens the popup and moves focus to the highlighted daySegment
ArrowDownOn a segment: steps the segment as in DateField; it does not open the popupSegment
EnterSpaceOn a day: chooses it, closes the popup (with closeOnSelect) and returns focusPopup
EscapeCloses the popup and returns focus to the trigger, or to the segment Alt+ArrowDown was used onPopup
AltArrowUpInside the popup: closes itPopup
TabNon-modal: from the last element of the popup closes it and continues after the triggerPopup

Accessibility

What the parts set
PartSetsSo that
Controlrole="group", named by the Field label (or aria-label); the Field's validity, data-popup-openthe group is announced once, "Birthday, group", and the error is on the segments
Triggeraria-haspopup="dialog", aria-expanded, aria-controls; aria-label "Choose date" or "Change date, Saturday, November 7, 2026"the button says what it does and what the date is now
Triggeraria-disabled while readOnlya read only picker does not open, and says so
Popuprole="dialog" named "Choose date" (or your aria-label); aria-modal only with modala screen reader announces the dialog on open; the page is not hidden unless it is inert
Closea button, "Close"a touch screen reader can leave a popup it cannot Escape

Focus order

  1. Each segment is a tab stop, then Clear is out of the order, then the Trigger. Nothing takes focus on mount.
  2. Opening moves focus into the popup, to the calendar day; presets and the Close button come in DOM order around it.
  3. Closing returns focus to the trigger, or to the segment the popup was opened from.
  4. Tab from the last tabbable element of a non-modal popup closes it and moves to the next element after the trigger.