InputsReact
A month grid for choosing a start and an end date.
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:
<RangeCalendar.Root>
<RangeCalendar.PrevButton />
<RangeCalendar.Heading />
<RangeCalendar.NextButton />
<RangeCalendar.Grid>
<RangeCalendar.GridHeader>
<RangeCalendar.GridRow>
<RangeCalendar.HeaderCell />
</RangeCalendar.GridRow>
</RangeCalendar.GridHeader>
<RangeCalendar.GridBody>
<RangeCalendar.Cell>
<RangeCalendar.Day />
</RangeCalendar.Cell>
</RangeCalendar.GridBody>
</RangeCalendar.Grid>
</RangeCalendar.Root>Examples
The demo at the top is the one example this component has so far.
API reference
33 props across 11 parts, in React syntax. className, style, render are listed once, under Shared props.
CellRangeCalendar.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. |
DayRangeCalendar.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. |
GridRangeCalendar.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) | — |
GridBodyRangeCalendar.GridBody
The body of a grid: the rows of weeks. Renders a <tbody>.
GridHeaderRangeCalendar.GridHeader
The header of a grid: put a Calendar.GridRow of Calendar.HeaderCell in it. Renders a <thead>.
GridRowRangeCalendar.GridRow
A row of a grid: a week, or the row of weekday names. Renders a <tr>.
HeaderCellRangeCalendar.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 | — |
HeadingRangeCalendar.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 |
NextButtonRangeCalendar.NextButton
Shows the next month(s). It is aria-disabled, not disabled, at the max bound, so it keeps focus. Renders a <button>.
PrevButtonRangeCalendar.PrevButton
Shows the previous month(s). It is aria-disabled, not disabled, at the min bound, so it keeps focus. Renders a <button>.
RootRangeCalendar.Root
Groups all parts of a calendar that selects a range of dates. The first press sets the start, the second commits the range; arrows or the pointer preview it and Escape cancels. Values are { start, end } with ISO 8601 strings. Renders a <div>.
| Prop | Type | Default |
|---|---|---|
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> | — |
valueThe selected range, null for none. | DateRange | null | — |
defaultValueThe selected range when initially rendered. | DateRange | null | — |
onValueChangeEvent handler called when a range is committed (the second press). | ((value: DateRange, eventDetails: CalendarRootChangeEventDetails) => void) | — |
| 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 |
|---|---|---|
| EnterSpace | Sets the start of a range; the second press commits it | Cell |
| HomeEndPageUpPageDown | Move focus as in Calendar; with a start set, the range from the start to the focused day is previewed | Cell |
| Escape | Cancels the pending range; with none pending it is not consumed, so a popup around it can close | Cell |
| Enter | Does nothing: isDateUnavailable(date, anchor) gets the start, so it can refuse ends that cross a booked night | Cell |
Accessibility
| Part | Sets | So that |
|---|---|---|
| Root | role="group", data-range | the range calendar is one named control |
| Grid | role="grid", aria-multiselectable="true" | the days of the range are read as several selected cells |
| Cell | aria-selected="true" on every day from the start to the end; the preview is not selection | a screen reader reads the committed range, not the pending one |
| Status | a visually hidden role="status" after the root | "From Tuesday, November 10, 2026, choose the end date", the commit and the cancel are announced once, not every arrow key |
Focus order
PrevButton, thenNextButton, in the order you render them.- The grid is one tab stop, chosen as in Calendar: the day that last had focus, else a selected day, else today.
- Arrow keys move focus inside the grid and extend the preview while a start is set.
Tableaves the grid; the pending start stays until a second press,Escapeor a new value.