InputsReact
A month grid for choosing one date, or several with multiple.
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:
<Calendar.Root>
<Calendar.PrevButton />
<Calendar.Heading />
<Calendar.NextButton />
<Calendar.Grid>
<Calendar.GridHeader>
<Calendar.GridRow>
<Calendar.HeaderCell />
</Calendar.GridRow>
</Calendar.GridHeader>
<Calendar.GridBody>
<Calendar.Cell>
<Calendar.Day />
</Calendar.Cell>
</Calendar.GridBody>
</Calendar.Grid>
</Calendar.Root>Examples
Limits
min and max disable the days outside November 3 to December 18 and the Previous and Next buttons at the bounds; isDateUnavailable books the weekends, which take focus but cannot be chosen. The rule is in text, linked with aria-describedby.
Multiple
multiple toggles days and reports them in ascending order; maxSelected stops at five.
Two Months
visibleMonths={2} with a Heading and a Grid per offset; Previous and Next move both months, and a date the other month shows is a hidden, empty cell.
Form
Inside Field and Form: required blocks the submit until a day is chosen, and the date submits as arrival=2026-11-12 from a hidden input type="date".
Locale
ar-EG in right to left (Arabic-Indic digits, weeks from Saturday, the arrow keys swapped), with a switcher to de-DE and en-US and firstDayOfWeek to start on Monday.
Controlled Month
visibleMonth and onVisibleMonthChange keep the month in state; buttons outside set it, and the readout shows the last details.reason (pointer, keyboard or none).
API reference
34 props across 11 parts, in React syntax. className, style, render are listed once, under Shared props.
CellCalendar.Cell
A cell of a week, and the focus holder of its date (APG): it is the roving tab stop, carries aria-selected, aria-disabled and aria-current, is named by the full date, and is pressed by click, Enter and Space. Put a Calendar.Day inside for styling. Renders a <td>.
| Prop | Type | Default |
|---|---|---|
daterequiredThe date of the cell, '2026-11-07'. | `${number}-${number}-${number}` | — |
| Data attribute | Description |
|---|---|
data-selected | Present when the date is selected (every date of a selected range). |
data-today | Present on today's date. |
data-disabled | Present when the date cannot be focused or chosen: before min, after max, or disabled. |
data-unavailable | Present when the date can be focused but not chosen (isDateUnavailable). |
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-highlighted | Present when the date has focus. |
data-range-start | Range: present on the first date of the selected range. |
data-range-end | Range: present on the last date of the selected range. |
data-in-range | Range: present on a date between the ends of the selected range. |
data-preview | Range: present on the dates of the pending range (anchor to hovered or focused date). |
data-preview-start | Range: present on the first date of the pending range. |
data-preview-end | Range: present on the last date of the pending range. |
DayCalendar.Day
The content of a Calendar.Cell: the day number, for styling. It is presentational (no role, no tab stop): the cell holds focus and the state. It carries the same data-* attributes as the cell and renders nothing for an outside day that another visible month shows. Renders a <span>.
| Data attribute | Description |
|---|---|
data-selected | Present when the date is selected (every date of a selected range). |
data-today | Present on today's date. |
data-disabled | Present when the date cannot be focused or chosen: before min, after max, or disabled. |
data-unavailable | Present when the date can be focused but not chosen (isDateUnavailable). |
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-highlighted | Present when the date has focus. |
data-range-start | Range: present on the first date of the selected range. |
data-range-end | Range: present on the last date of the selected range. |
data-in-range | Range: present on a date between the ends of the selected range. |
data-preview | Range: present on the dates of the pending range (anchor to hovered or focused date). |
data-preview-start | Range: present on the first date of the pending range. |
data-preview-end | Range: present on the last date of the pending range. |
GridCalendar.Grid
The grid of one month. Its children are a function of the month: weeks (the days of each week), weekdays (the header cells, starting with the first day of the week), month and label. Renders a <table>.
| Prop | Type | Default |
|---|---|---|
offsetWhich of the visible months this grid shows: 0 is the first. | number | 0 |
childrenThe content, or a function of the month's weeks and weekdays. | React.ReactNode | ((grid: CalendarGrid) => React.ReactNode) | — |
GridBodyCalendar.GridBody
The body of a grid: the rows of weeks. Renders a <tbody>.
GridHeaderCalendar.GridHeader
The header of a grid: put a Calendar.GridRow of Calendar.HeaderCell in it. Renders a <thead>.
GridRowCalendar.GridRow
A row of a grid: a week, or the row of weekday names. Renders a <tr>.
HeaderCellCalendar.HeaderCell
The name of a weekday above its column. abbr is the long name. Renders a <th>.
| Prop | Type | Default |
|---|---|---|
weekdayrequiredThe weekday of the column: 0 is Sunday, 6 is Saturday. | number | — |
HeadingCalendar.Heading
The name of a visible month, for example "November 2026", and the label of its grid. The first one is a polite live region, so a screen reader announces a new month. Renders a <h2>.
| Prop | Type | Default |
|---|---|---|
offsetWhich of the visible months this heading names: 0 is the first. | number | 0 |
NextButtonCalendar.NextButton
Shows the next month(s). It is aria-disabled, not disabled, at the max bound, so it keeps focus. Renders a <button>.
PrevButtonCalendar.PrevButton
Shows the previous month(s). It is aria-disabled, not disabled, at the min bound, so it keeps focus. Renders a <button>.
RootCalendar.Root
Groups all parts of a calendar that selects one date, or several with multiple. Values are ISO 8601 strings such as '2026-11-07'. Renders a <div>.
| Prop | Type | Default |
|---|---|---|
valueThe selected date, null for none. | CalendarInputValue<Multiple> | null | — |
defaultValueThe selected date when initially rendered. | CalendarInputValue<Multiple> | null | — |
onValueChangeEvent handler called when the selection changes. | ((value: CalendarOutputValue<Multiple>, eventDetails: CalendarRootChangeEventDetails) => void) | — |
multipleWhether several dates can be selected. | Multiple | false |
minThe earliest date that can be focused or chosen ('2026-11-07'). | `${number}-${number}-${number}` | — |
maxThe latest date that can be focused or chosen. | `${number}-${number}-${number}` | — |
isDateUnavailableWhether a date can be focused but not chosen. | ((date: `${number}-${number}-${number}`, anchor: `${number}-${number}-${number}` | null) => boolean) | — |
isDateDisabledWhether a date is disabled: navigation skips it and it cannot be chosen. | ((date: `${number}-${number}-${number}`) => 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 date 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 month names, weekday names, digits and first day of the week. | string | 'en-US' |
firstDayOfWeekThe first day of the week, 0 (Sunday) to 6 (Saturday). | number | — |
visibleMonthThe first visible month ('2026-11' or a date). | `${number}-${number}-${number}` | `${number}-${number}` | null | — |
defaultVisibleMonthThe first visible month when initially rendered. | `${number}-${number}-${number}` | `${number}-${number}` | — |
onVisibleMonthChangeEvent handler called when the first visible month changes. | ((month: `${number}-${number}`, eventDetails: CalendarRootVisibleMonthChangeEventDetails) => void) | — |
visibleMonthsThe number of months shown. | number | 1 |
pageBehaviorWhether Previous and Next move by the number of visible months or by one. | CalendarPageBehavior | 'visible' |
fixedWeeksWhether every month shows six weeks. | boolean | false |
todayThe date of today ('2026-11-07'). | `${number}-${number}-${number}` | — |
timeZoneThe time zone that decides what today is, when today is not given. | string | — |
highlightedDateThe date that holds focus. | `${number}-${number}-${number}` | null | — |
onHighlightChangeEvent handler called when the focused date changes. | ((date: `${number}-${number}-${number}` | null) => void) | — |
disallowEmptySelectionWhether the last selected date cannot be deselected. | boolean | false |
maxSelectedmultiple only: the most dates that can be selected. | number | — |
labelsThe texts the calendar adds: the Previous and Next labels and the words after a date's name. | Partial<CalendarLabels> | — |
inputRefA ref to access the hidden input element. | React.Ref<HTMLInputElement> | — |
| Data attribute | Description |
|---|---|
data-disabled | Present when the calendar is disabled. |
data-readonly | Present when the calendar does not allow changing the selection. |
data-required | Present when the calendar is required. |
data-multiple | Present when the calendar allows several selected dates. |
data-range | Present in a range calendar. |
data-empty | Present when nothing is selected. |
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 |
|---|---|---|
| ArrowLeftArrowRight | Moves focus a day, without selecting; past the first or last day it shows the next month | Cell |
| ArrowUpArrowDown | Moves focus a week | Cell |
| HomeEnd | Moves focus to the first or last day of the locale's week (or of firstDayOfWeek) | Cell |
| PageUpPageDown | Moves a month, keeping the day number or the last day of a shorter month; with Shift, a year | Cell |
| EnterSpace | Selects the focused day; with multiple, toggles it; an unavailable day is not chosen | Cell |
| ArrowLeftArrowRight | Swapped: ArrowLeft moves to the next day; Home and End stay on the locale's week | Cell |
| minmax | Stay inside the bounds and skip disabled days; unavailable days still take focus | Cell |
| Tab | Enters on the day that last had focus, else the selected day, else today, else the first enabled day | Grid |
| EnterSpace | Shows the previous or next month (reason: 'keyboard'); at 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 | the calendar is one named control; no aria-invalid on the group, the Field error says what is wrong |
| Heading | h2, aria-live="polite" and aria-atomic on the first one | a new month is announced when the buttons or the keys change it, and not on the first render |
| Grid | role="grid", aria-labelledby its Heading (aria-label with the month as the fallback), aria-multiselectable | each month is read by its name, and whether several days can be chosen |
| 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" |
| Cell | role="gridcell", roving tabindex, aria-selected, aria-disabled, aria-current="date", aria-label the full date | the focused element carries the state, and is read "Thursday, November 12, 2026, selected" |
| Day | nothing: no role and no tab stop | the number inside the cell only styles; the cell's name already contains it |
| PrevButton, NextButton | aria-label ("Previous month", "Next month" from labels), aria-disabled at the bound, never disabled | the buttons keep focus at min or max and say they do nothing |
Focus order
PrevButton, thenNextButton, in the order you render them.- The grid is one tab stop: the day that last had focus when it is still visible, else the selected day, else today, else the first enabled day. Nothing takes focus on mount.
- Arrow keys,
Home,End,PageUpandPageDownmove focus inside the grid, skip disabled days and change the visible month when they leave it. Keys are handled only on a cell; there are no document-level listeners. Tableaves the grid.