InputsReact
A month grid for choosing a whole week.
Rendered by React 19.3.1
Loading React…Installation
npm i @hamur/reactNot on npm yet. The package goes out under the chosen name.
Anatomy
Import the component and assemble its parts:
<WeekCalendar.Root>
<WeekCalendar.PrevButton />
<WeekCalendar.Heading />
<WeekCalendar.NextButton />
<WeekCalendar.Grid>
<WeekCalendar.GridHeader>
<WeekCalendar.GridRow>
<WeekCalendar.WeekNumberHeaderCell />
<WeekCalendar.HeaderCell />
</WeekCalendar.GridRow>
</WeekCalendar.GridHeader>
<WeekCalendar.GridBody>
<WeekCalendar.Row>
<WeekCalendar.WeekNumber />
<WeekCalendar.Cell>
<WeekCalendar.Day />
</WeekCalendar.Cell>
</WeekCalendar.Row>
</WeekCalendar.GridBody>
</WeekCalendar.Grid>
</WeekCalendar.Root>Examples
Iso
weekRule switched between 'iso' and 'locale' in January 2027: the same day is in week 53 of 2026 or week 1 of 2027, read with getWeekOfDate and written with formatWeekString.
Limits
min and max disable the weeks outside November 8 to December 13 and the buttons at the bounds; isWeekUnavailable books one week, which takes focus but cannot be chosen.
Form
Inside Field and Form: required blocks the submit until a week is chosen, and the first day submits as week=2026-11-15.
API reference
31 props across 14 parts, in React syntax. className, style, render are listed once, under Shared props.
CellWeekCalendar.Cell
A day of a week, named by its full date. It is not focusable and carries no aria-selected: the row holds focus and the selection. Put a WeekCalendar.Day inside for styling. Renders a <td>.
| Prop | Type | Default |
|---|---|---|
daterequiredThe date of the cell, '2026-11-08'. | `${number}-${number}-${number}` | — |
| Data attribute | Description |
|---|---|
data-selected | Present when the cell's week is selected. |
data-today | Present on today's date. |
data-disabled | Present when the cell's week cannot be focused or chosen. |
data-unavailable | Present when the cell's week can be focused but not chosen. |
data-outside-month | Present on a date that belongs to the previous or the next month. |
data-weekend | Present on a date of the locale's weekend. |
data-value | The date, YYYY-MM-DD. |
DayWeekCalendar.Day
The content of a WeekCalendar.Cell: the day number in the locale's digits, for styling. It carries the same data-* attributes as the cell. Renders a <span>.
| Data attribute | Description |
|---|---|
data-selected | Present when the cell's week is selected. |
data-today | Present on today's date. |
data-disabled | Present when the cell's week cannot be focused or chosen. |
data-unavailable | Present when the cell's week can be focused but not chosen. |
data-outside-month | Present on a date that belongs to the previous or the next month. |
data-weekend | Present on a date of the locale's weekend. |
data-value | The date, YYYY-MM-DD. |
GridWeekCalendar.Grid
The grid of the visible month, one row per week. Its children are a function of the month: weeks (each week with its number and days), weekdays (the header cells, starting with the first day of the week), month and label. Renders a <table>.
| Prop | Type | Default |
|---|---|---|
childrenThe content, or a function of the month's weeks and weekdays. | React.ReactNode | ((grid: WeekCalendarGrid) => React.ReactNode) | — |
GridBodyWeekCalendar.GridBody
The body of a grid: the rows of weeks. Renders a <tbody>.
GridHeaderWeekCalendar.GridHeader
The header of a grid: put a Calendar.GridRow of Calendar.HeaderCell in it. Renders a <thead>.
GridRowWeekCalendar.GridRow
A row of a grid: a week, or the row of weekday names. Renders a <tr>.
HeaderCellWeekCalendar.HeaderCell
The name of a weekday above its column. abbr and aria-label are the long name. Renders a <th>.
| Prop | Type | Default |
|---|---|---|
weekdayrequiredThe weekday of the column: 0 is Sunday, 6 is Saturday. | number | — |
HeadingWeekCalendar.Heading
The name of the visible month, for example "November 2026", and the label of the grid. It is a polite live region, so a screen reader announces a new month. Renders a <h2>.
NextButtonWeekCalendar.NextButton
Shows the next month. It is aria-disabled, not disabled, at the max bound, so it keeps focus. Renders a <button>.
| Data attribute | Description |
|---|---|
data-disabled | Present when there is no later month to show, or the week calendar is disabled. |
PrevButtonWeekCalendar.PrevButton
Shows the previous month. It is aria-disabled, not disabled, at the min bound, so it keeps focus. Renders a <button>.
| Data attribute | Description |
|---|---|
data-disabled | Present when there is no earlier month to show, or the week calendar is disabled. |
RootWeekCalendar.Root
Groups all parts of a calendar in which a week is the thing chosen. The value is the week's first day as an ISO 8601 string such as '2026-11-08'; any day is read as the first day of its week. Renders a <div>.
| Prop | Type | Default |
|---|---|---|
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. | boolean | false |
readOnlyWhether the user should be unable to change the selection. | boolean | false |
requiredWhether the user must choose a week before submitting a form. | boolean | false |
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 the | WeekRuleName | '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. | boolean | false |
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 selected week's first day, null for none. | `${number}-${number}-${number}` | null | — |
defaultValueThe selected week when initially rendered. | `${number}-${number}-${number}` | null | — |
onValueChangeEvent handler called when the selection changes, with the week's first day, or null when | ((value: `${number}-${number}-${number}` | null, eventDetails: WeekCalendarRootChangeEventDetails) => void) | — |
defaultVisibleMonthThe first visible month when initially rendered. | `${number}-${number}-${number}` | `${number}-${number}` | — |
highlightedWeekThe first day of the week that holds focus. | `${number}-${number}-${number}` | null | — |
onHighlightChangeEvent handler called when the focused week changes, with its first day. | ((start: `${number}-${number}-${number}` | null) => void) | — |
disallowEmptySelectionWhether the selected week cannot be deselected. | boolean | false |
labelsThe texts the calendar adds: the button labels, the week names and the status messages. | Partial<WeekCalendarLabels> | — |
| Data attribute | Description |
|---|---|
data-disabled | Present when the week calendar is disabled. |
data-readonly | Present when the week calendar does not allow changing the selection. |
data-required | Present when the week calendar is required. |
data-empty | Present when no week is selected. |
data-valid | Present when the week calendar is in a valid state (when wrapped in Field.Root). |
data-invalid | Present when the week calendar is in an invalid state (when wrapped in Field.Root). |
data-touched | Present when the week calendar has been touched (when wrapped in Field.Root). |
data-dirty | Present when the week calendar's value has changed (when wrapped in Field.Root). |
data-filled | Present when a week is selected (when wrapped in Field.Root). |
data-focused | Present when a row of the week calendar is focused (when wrapped in Field.Root). |
RowWeekCalendar.Row
A week, and the thing chosen: the roving tab stop, aria-selected and the week's name are on the row. A click anywhere in it, Enter or Space chooses the week. Renders a <tr>.
| Prop | Type | Default |
|---|---|---|
valuerequiredThe week's first day, week.start of one of the grid's weeks. | `${number}-${number}-${number}` | — |
| Data attribute | Description |
|---|---|
data-selected | Present when the week is selected. |
data-current | Present on the week that holds today. |
data-disabled | Present when the week cannot be focused or chosen: its first day is before min or after max, or isWeekDisabled. |
data-unavailable | Present when the week can be focused but not chosen (isWeekUnavailable). |
data-highlighted | Present when the week has focus. |
data-value | The week's first day, YYYY-MM-DD. |
data-week | The week's number in its week-year. |
data-week-year | The week-year of the week. |
WeekNumberWeekCalendar.WeekNumber
The number of the row's week, in the locale's digits, as the row's header. Named "Week 46". Renders a <th>.
| Data attribute | Description |
|---|---|
data-selected | Present when the week is selected. |
data-current | Present on the week that holds today. |
data-disabled | Present when the week cannot be focused or chosen. |
data-unavailable | Present when the week can be focused but not chosen. |
WeekNumberHeaderCellWeekCalendar.WeekNumberHeaderCell
The header of the week number column, named "Week number". Its children are the visible text. Renders a <th>.
Shared props
Every part that renders an element takes these.
| Prop | Type |
|---|---|
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
| Keys | Does | Part |
|---|---|---|
| ArrowUpArrowDown | Moves focus a week, without choosing; past the first or last row it shows the next month | Row |
| ArrowLeftArrowRight | Also moves focus a week: the row is the item, so there is no day to move to | Row |
| ArrowLeftArrowRight | Swapped: ArrowLeft moves to the next week | Row |
| HomeEnd | Moves focus to the first or last enabled week of the grid | Row |
| PageUpPageDown | Moves to the week a month earlier or later and shows that month; with Shift, a year | Row |
| EnterSpace | Chooses the focused week; on the selected week, clears it unless disallowEmptySelection | Row |
| Enter | Chooses nothing; the week keeps focus | Row |
| minmax | Stay inside the bounds and pass over disabled weeks | Row |
| Tab | Enters on the week that last had focus, else the selected week, else the current week, else the first one | Grid |
| EnterSpace | Shows the previous or next month; in the month of a bound the button keeps focus and does nothing | PrevButton |
Accessibility
| Part | Sets | So that |
|---|---|---|
| Root | role="group", named by aria-label or Field.Label; a polite status region inside it | the calendar is one named control, and a chosen or cleared week is said once |
| Heading | h2, aria-live="polite" and aria-atomic | a new month is announced when the buttons or the keys change it |
| Grid | role="grid", aria-labelledby its Heading (aria-label with the month as the fallback), aria-readonly, aria-disabled | the month is read by its name |
| WeekNumberHeaderCell | scope="col", aria-label "Week number" | the column is read as "Week number" whatever short text it shows |
| HeaderCell | scope="col", the short name as text and the long name as aria-label and abbr | the weekday column is read as "Monday", not "Mo" |
| Row | role="row", roving tabindex, aria-selected, aria-disabled, aria-label such as "Week 46, November 8 to 14, 2026" | the focused row carries the week's name and state: "current week", "selected", "unavailable" |
| WeekNumber | role="rowheader", scope="row", aria-label "Week 46" | the number names its row when a screen reader reads by table cell |
| Cell | role="gridcell", no tab stop and no aria-selected; aria-label the full date; aria-current="date" on today | the days are read for context while the row is what is chosen |
| PrevButton | aria-label "Previous month" from labels, aria-disabled at a bound, never disabled | the button keeps focus at min and says it does nothing |
| NextButton | aria-label "Next month" from labels, aria-disabled at a bound, never disabled | the button keeps focus at max and says it does nothing |
Focus order
PrevButton, thenNextButton, in the order you render them.- The grid is one tab stop, on a row: the week that last had focus when it is still visible, else the selected week, else the current week, else the first enabled week. Nothing takes focus on mount.
- The arrow keys,
Home,End,PageUpandPageDownmove focus between rows, pass over disabled weeks and change the visible month when they leave it. The day cells never take focus. Tableaves the grid.