Skip to content

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

InputsReact

Week Picker

A button that shows the chosen week and opens a WeekCalendar in a popup.

Rendered by React 19.3.1

Loading React…
import { Field } from '@hamur/react/field'
import { WeekCalendar } from '@hamur/react/week-calendar'
import { WeekPicker } from '@hamur/react/week-picker'

export default function WeekPickerExample() {
  return (
    <Field.Root className="field">
      <Field.Label>Reporting week</Field.Label>
      <WeekPicker.Root locale="en-US" today="2026-11-11">
        <WeekPicker.Trigger className="week-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>
          <WeekPicker.Value className="week-picker-value" />
        </WeekPicker.Trigger>
        <WeekPicker.Portal>
          <WeekPicker.Positioner sideOffset={8}>
            <WeekPicker.Popup className="popover date-picker-popup">
              <WeekPicker.Calendar className="calendar">
                <div className="calendar-header">
                  <WeekCalendar.PrevButton className="calendar-nav">‹</WeekCalendar.PrevButton>
                  <WeekCalendar.Heading className="calendar-heading" />
                  <WeekCalendar.NextButton className="calendar-nav">›</WeekCalendar.NextButton>
                </div>
                <WeekCalendar.Grid className="calendar-grid week-calendar-grid">
                  {(grid) => (
                    <>
                      <WeekCalendar.GridHeader>
                        <WeekCalendar.GridRow>
                          <WeekCalendar.WeekNumberHeaderCell className="calendar-weekday">
                            Wk
                          </WeekCalendar.WeekNumberHeaderCell>
                          {grid.weekdays.map((weekday) => (
                            <WeekCalendar.HeaderCell
                              key={weekday.index}
                              weekday={weekday.index}
                              className="calendar-weekday"
                            />
                          ))}
                        </WeekCalendar.GridRow>
                      </WeekCalendar.GridHeader>
                      <WeekCalendar.GridBody>
                        {grid.weeks.map(({ week, days }) => (
                          <WeekCalendar.Row
                            key={week.start}
                            value={week.start}
                            className="week-calendar-row"
                          >
                            <WeekCalendar.WeekNumber className="week-calendar-number" />
                            {days.map((day) => (
                              <WeekCalendar.Cell
                                key={day.date}
                                date={day.date}
                                className="week-calendar-cell"
                              >
                                <WeekCalendar.Day className="calendar-day" />
                              </WeekCalendar.Cell>
                            ))}
                          </WeekCalendar.Row>
                        ))}
                      </WeekCalendar.GridBody>
                    </>
                  )}
                </WeekCalendar.Grid>
              </WeekPicker.Calendar>
              <WeekPicker.Close className="button date-picker-close">
                Close
              </WeekPicker.Close>
            </WeekPicker.Popup>
          </WeekPicker.Positioner>
        </WeekPicker.Portal>
      </WeekPicker.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:

<WeekPicker.Root>
  <WeekPicker.Trigger>
    <WeekPicker.Value />
  </WeekPicker.Trigger>
  <WeekPicker.Portal>
    <WeekPicker.Positioner>
      <WeekPicker.Popup>
        <WeekPicker.Calendar />
        <WeekPicker.Close />
      </WeekPicker.Popup>
    </WeekPicker.Positioner>
  </WeekPicker.Portal>
</WeekPicker.Root>

Examples

Form

Inside Field and Form with required, min and max: the weeks outside the bounds are disabled and the first day submits as leave=2026-11-22.

WeekPickerForm.tsx
import * as React from 'react'
import { Field } from '@hamur/react/field'
import { Form } from '@hamur/react/form'
import { WeekCalendar } from '@hamur/react/week-calendar'
import { WeekPicker } from '@hamur/react/week-picker'

export default function WeekPickerFormExample() {
  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="leave" className="field">
        <Field.Label>Week of leave</Field.Label>
        <WeekPicker.Root
          locale="en-US"
          today="2026-11-11"
          min="2026-11-08"
          max="2026-12-20"
          required
        >
          <WeekPicker.Trigger className="week-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>
            <WeekPicker.Value className="week-picker-value" />
          </WeekPicker.Trigger>
          <WeekPicker.Portal>
            <WeekPicker.Positioner sideOffset={8}>
              <WeekPicker.Popup className="popover date-picker-popup">
                <WeekPicker.Calendar className="calendar">
                  <div className="calendar-header">
                    <WeekCalendar.PrevButton className="calendar-nav">‹</WeekCalendar.PrevButton>
                    <WeekCalendar.Heading className="calendar-heading" />
                    <WeekCalendar.NextButton className="calendar-nav">›</WeekCalendar.NextButton>
                  </div>
                  <WeekCalendar.Grid className="calendar-grid week-calendar-grid">
                    {(grid) => (
                      <>
                        <WeekCalendar.GridHeader>
                          <WeekCalendar.GridRow>
                            <WeekCalendar.WeekNumberHeaderCell className="calendar-weekday">
                              Wk
                            </WeekCalendar.WeekNumberHeaderCell>
                            {grid.weekdays.map((weekday) => (
                              <WeekCalendar.HeaderCell
                                key={weekday.index}
                                weekday={weekday.index}
                                className="calendar-weekday"
                              />
                            ))}
                          </WeekCalendar.GridRow>
                        </WeekCalendar.GridHeader>
                        <WeekCalendar.GridBody>
                          {grid.weeks.map(({ week, days }) => (
                            <WeekCalendar.Row
                              key={week.start}
                              value={week.start}
                              className="week-calendar-row"
                            >
                              <WeekCalendar.WeekNumber className="week-calendar-number" />
                              {days.map((day) => (
                                <WeekCalendar.Cell
                                  key={day.date}
                                  date={day.date}
                                  className="week-calendar-cell"
                                >
                                  <WeekCalendar.Day className="calendar-day" />
                                </WeekCalendar.Cell>
                              ))}
                            </WeekCalendar.Row>
                          ))}
                        </WeekCalendar.GridBody>
                      </>
                    )}
                  </WeekCalendar.Grid>
                </WeekPicker.Calendar>
              </WeekPicker.Popup>
            </WeekPicker.Positioner>
          </WeekPicker.Portal>
        </WeekPicker.Root>
        <Field.Description className="muted">From November 8 to December 20.</Field.Description>
        <Field.Error className="error" />
      </Field.Root>
      <button className="button" type="submit">
        Submit
      </button>
      {submitted ? <pre className="output">{submitted}</pre> : null}
    </Form>
  )
}

API reference

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

CalendarWeekPicker.Calendar

The calendar of the popup: a WeekCalendar.Root wired to the picker's value, limits, rule and visible month. Compose it with the WeekCalendar parts. It takes no part in the Field: the picker's root already submits and validates the value. Renders a <div>.

Calendar data attributes
Data attributeDescription
data-disabledPresent when the week picker is disabled.
data-readonlyPresent when the week picker does not allow changing the selection.
data-emptyPresent when no week is chosen.

CloseWeekPicker.Close

A button that closes the popup, named "Close" unless you name it. Renders a <button>.

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

PopupWeekPicker.Popup

The dialog that holds the week calendar, named "Choose week" unless you name it. Focus moves to the calendar's tab stop week when it opens and returns to the trigger 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)—
Popup data attributes
Data attributeDescription
data-openPresent when the popup is open.
data-closedPresent when the popup is closed.
data-starting-stylePresent when the popup begins animating in.
data-ending-stylePresent when the popup is animating out.
data-sideIndicates which side the popup is positioned relative to the trigger.
data-alignIndicates how the popup is aligned relative to specified side.
data-instantPresent if animations should be instant.

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

PositionerWeekPicker.Positioner

Positions the popup against the trigger. Renders a <div>.

Positioner props
PropTypeDefault
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—
alignHow to align the popup relative to the specified side.Align'start'
Positioner data attributes
Data attributeDescription
data-openPresent when the popup is open.
data-closedPresent when the popup is closed.
data-anchor-hiddenPresent when the anchor is hidden.
data-sideIndicates which side the popup is positioned relative to the trigger.
data-alignIndicates how the popup is aligned relative to specified side.

RootWeekPicker.Root

Groups all parts of a button that shows the chosen week and opens a week calendar in a popup. It renders no element of its own, only the hidden input. The value is the week's first day as an ISO 8601 string such as '2026-11-08'.

Root props
PropTypeDefault
minThe earliest first day of a week that can be focused or chosen ('2026-11-09').`${number}-${number}-${number}`—
maxThe latest first day of a week that can be focused or chosen.`${number}-${number}-${number}`—
isWeekDisabledWhether a week is disabled: navigation skips it and it cannot be chosen.((week: Week) => boolean)—
isWeekUnavailableWhether a week can be focused but not chosen.((week: Week) => boolean)—
disabledWhether the component should ignore user interaction.booleanfalse
readOnlyWhether the user should be unable to change the selection.booleanfalse
requiredWhether the user must choose a week before submitting a form.booleanfalse
nameIdentifies the field when a form is submitted.string—
formIdentifies the form that owns the hidden input.string—
localeThe BCP 47 locale of the names, the digits and the week rule.string'en-US'
weekRuleThe week rule: the locale's, or ISO 8601's (weeks start on Monday, and week 1 holds theWeekRuleName'locale'
firstDayOfWeekThe first day of the week, 0 (Sunday) to 6 (Saturday).number—
minimalDaysThe days, 1 to 7, the first week of a year has in that year.number—
visibleMonthThe visible month ('2026-11' or a date).`${number}-${number}-${number}` | `${number}-${number}` | null—
onVisibleMonthChangeEvent handler called when the visible month changes.((month: `${number}-${number}`, eventDetails: WeekCalendarRootVisibleMonthChangeEventDetails) => void)—
fixedWeeksWhether every month shows six weeks.booleanfalse
todayThe date of today ('2026-11-11').`${number}-${number}-${number}`—
timeZoneThe time zone that decides what today is, when today is not given.string—
inputRefA ref to access the hidden input element.React.Ref<HTMLInputElement>—
valueThe chosen week's first day, null for none.`${number}-${number}-${number}` | null—
defaultValueThe chosen week when initially rendered.`${number}-${number}-${number}` | null—
onValueChangeEvent handler called when a week is chosen, with its first day.((value: `${number}-${number}-${number}` | null, eventDetails: WeekCalendarRootChangeEventDetails) => void)—
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: WeekPickerRootOpenChangeEventDetails) => 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 week closes the popup.booleantrue
defaultVisibleMonthThe month the calendar shows before the popup first opens.`${number}-${number}-${number}` | `${number}-${number}`—
highlightedWeekThe first day of the week that holds focus in the open calendar.`${number}-${number}-${number}` | null—
onHighlightChangeEvent handler called when the focused week changes, null when focus leaves the calendar.((start: `${number}-${number}-${number}` | null) => void)—
labelsThe texts the picker and its calendar add, each overridable.Partial<WeekPickerLabels>—
childrenReact.ReactNode—

TriggerWeekPicker.Trigger

A button that shows the chosen week and opens the popup. Inside a Field it is named by the Field's label and its own text. Renders a <button>.

Trigger props
PropTypeDefault
nativeButtonWhether the component renders a native <button> element when replacing itbooleantrue
idID of the trigger.string—
Trigger data attributes
Data attributeDescription
data-popup-openPresent when the corresponding popup is open.
data-pressedPresent when the trigger is pressed.
data-disabledPresent when the trigger is disabled.
data-placeholderPresent when no week is chosen and the value shows the placeholder.
data-validPresent when the week picker is in a valid state (when wrapped in Field.Root).
data-invalidPresent when the week picker is in an invalid state (when wrapped in Field.Root).
data-touchedPresent when the week picker has been touched (when wrapped in Field.Root).
data-dirtyPresent when the week picker's value has changed (when wrapped in Field.Root).
data-filledPresent when a week is chosen (when wrapped in Field.Root).
data-focusedPresent when the trigger is focused (when wrapped in Field.Root).

ValueWeekPicker.Value

The chosen week as text ("Week 46, Nov 8 – 14, 2026"), or the placeholder when none is chosen. Renders a <span>.

Value props
PropTypeDefault
placeholderThe text shown when no week is chosen.React.ReactNode—
childrenThe content, or a function of the chosen week (null when none).React.ReactNode | ((week: Week | null) => React.ReactNode)—
Value data attributes
Data attributeDescription
data-placeholderPresent when no week is chosen and the placeholder shows.

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 chosen week, else the current weekTrigger
EnterSpaceOn a week: chooses it, closes the popup (with closeOnSelect) and returns focusPopup
EscapeCloses the popup and returns focus to the triggerPopup

Accessibility

What the parts set
PartSetsSo that
Triggeraria-haspopup="dialog", aria-expanded, aria-controls; aria-labelledby the Field label and itselfthe button says what it opens, what it is for and which week is chosen
Triggeraria-disabled while readOnly; the Field's validity as data-valid or data-invalida read only picker does not open, and says so
Valuethe week's short text, or the placeholder with data-placeholderthe trigger always has a visible name
Popuprole="dialog" named "Choose week" (or your aria-label); aria-modal only with modala screen reader announces the dialog on open
Calendarthe WeekCalendar root, named by the popup's label, with no hidden input and no part in the Fieldthe calendar is read by the dialog's name and the form gets the value once
Closea button, "Close"a touch screen reader can leave a popup it cannot Escape

Focus order

  1. The Trigger is one tab stop. Nothing takes focus on mount.
  2. Opening moves focus into the popup, to a week of the grid; the calendar's buttons and Close come in DOM order around it.
  3. Choosing a week or closing returns focus to the trigger.