DisplayReact
A calendar of events in time: a day, a work week or a week as a time grid, a month as a grid of days, or the next days as an agenda.
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:
<Scheduler.Root>
<Scheduler.PrevButton />
<Scheduler.TodayButton />
<Scheduler.NextButton />
<Scheduler.Heading />
<Scheduler.Grid>
<Scheduler.GridHeader>
<Scheduler.GridRow>
<Scheduler.HeaderCell />
<Scheduler.RowHeader />
<Scheduler.Slot>
<Scheduler.Event />
</Scheduler.Slot>
</Scheduler.GridRow>
</Scheduler.GridHeader>
<Scheduler.GridBody>
<Scheduler.Placeholder />
<Scheduler.NowIndicator />
</Scheduler.GridBody>
</Scheduler.Grid>
</Scheduler.Root>
// Not in this example: Scheduler.Agenda, Scheduler.AgendaDay, Scheduler.AgendaHeading, Scheduler.Empty, Scheduler.MoreButton, Scheduler.MoveHandle, Scheduler.ResizeHandleExamples
Drag
Events moved and resized by the pointer and by their handles; onEventChange writes the change into the app's events.
Month
A month that draws two events a day and a MoreButton for the rest, which shows them all.
Agenda
The next seven days as day groups; Enter on an event calls onAction, and an empty range shows Empty.
Unavailable
A booking grid whose lunch hour and Friday afternoon are unavailable; a selection reads back from value.
API reference
59 props across 22 parts, in React syntax. className, style, render are listed once, under Shared props.
AgendaScheduler.Agenda
The agenda view: the range's days, each a group of its events. Its children are a function of the agenda's model. Renders a <div>.
| Prop | Type | Default |
|---|---|---|
childrenThe days of the agenda, a function of its model (SchedulerAgendaData). | React.ReactNode | ((agenda: SchedulerAgendaData) => React.ReactNode) | — |
AgendaDayScheduler.AgendaDay
A day of the agenda, a group named by its AgendaHeading. Renders a <div>.
| Prop | Type | Default |
|---|---|---|
daterequiredThe day, a DateString. | string | — |
AgendaHeadingScheduler.AgendaHeading
The full date of an AgendaDay, which names it. Renders a <h3>.
EmptyScheduler.Empty
Shown only while the range has no events. Renders a <div>.
EventScheduler.Event
An event's segment on one day. Its handles are its siblings, right after it in the same Slot. Renders a <div>.
| Prop | Type | Default |
|---|---|---|
eventrequiredThe event, one of the Root's events. | SchedulerEvent<unknown> | — |
segmentThe segment of the event this part draws, from the Grid's or the Agenda's model. | SchedulerSegment | — |
disabledWhether the event cannot be activated, moved or resized. | boolean | — |
onActionCalled when the event is activated by a click, Enter or Space. | ((details: SchedulerEvent.ActionDetails) => void) | — |
GridScheduler.Grid
The time grid (day, week, workWeek) or the month grid. Its children are a function of the grid's model, which the app maps to rows, cells and events. Renders a <div>.
| Prop | Type | Default |
|---|---|---|
childrenThe rows of the grid, a function of the grid's model (SchedulerGridData). | React.ReactNode | ((grid: SchedulerGridData) => React.ReactNode) | — |
GridBodyScheduler.GridBody
The rows of slots. Renders a <div>.
GridHeaderScheduler.GridHeader
The rows above the slots: the day headers and the all-day row. Renders a <div>.
GridRowScheduler.GridRow
A row of the grid: the day headers, the all-day row, a row of slots or a week. Renders a <div>.
HeaderCellScheduler.HeaderCell
A column header: a day of the time grid (date), a weekday of the month grid (weekday), or the empty corner over the row headers. Renders a <div>.
| Prop | Type | Default |
|---|---|---|
dateThe day of a time grid column, a DateString. | string | — |
weekdayThe weekday of a month grid column, 0 being Sunday. | number | — |
HeadingScheduler.Heading
The range shown, which names the Root and the Grid and is spoken when it changes. Renders a <h2>.
MoreButtonScheduler.MoreButton
Stands for the events of a day the app does not draw. Its press is the app's. Renders a <button>.
| Prop | Type | Default |
|---|---|---|
nativeButtonWhether the component renders a native <button> element when replacing it | boolean | true |
daterequiredThe day, a DateString. | string | — |
countrequiredThe number of the day's events the app does not draw. | number | — |
MoveHandleScheduler.MoveHandle
Moves its event by pointer, touch, keys or click-to-place. Rendered right after its Event. Renders a <button>.
| Prop | Type | Default |
|---|---|---|
nativeButtonWhether the component renders a native <button> element when replacing it | boolean | true |
eventrequiredThe event whose handle this is. | SchedulerEvent<unknown> | — |
segmentThe segment of the event drawn by the Event this handle follows. | SchedulerSegment | — |
NextButtonScheduler.NextButton
Moves the range forward: a day, a week, a month or the agenda's days. Renders a <button>.
| Prop | Type | Default |
|---|---|---|
nativeButtonWhether the component renders a native <button> element when replacing it | boolean | true |
focusableWhenDisabledWhether the button stays focusable while the Root is disabled. | boolean | true |
NowIndicatorScheduler.NowIndicator
The current time, rendered by the app in today's first visible slot while now is inside the window (grid.now). Renders a <div>.
PlaceholderScheduler.Placeholder
The box of a pending move, resize or selection, rendered by the app in the Slot the Grid's model names (grid.placeholder.slot). Renders a <div>.
PrevButtonScheduler.PrevButton
Moves the range back: a day, a week, a month or the agenda's days. Renders a <button>.
| Prop | Type | Default |
|---|---|---|
nativeButtonWhether the component renders a native <button> element when replacing it | boolean | true |
focusableWhenDisabledWhether the button stays focusable while the Root is disabled. | boolean | true |
ResizeHandleScheduler.ResizeHandle
Moves the start or the end of its event. Rendered right after its Event and MoveHandle. Renders a <button>.
| Prop | Type | Default |
|---|---|---|
nativeButtonWhether the component renders a native <button> element when replacing it | boolean | true |
eventrequiredThe event whose handle this is. | SchedulerEvent<unknown> | — |
segmentThe segment of the event drawn by the Event this handle follows. | SchedulerSegment | — |
edgerequiredThe edge of the event this handle moves. | "start" | "end" | — |
RootScheduler.Root
Groups all parts of the scheduler: a calendar of events by day, week, work week, month or agenda, where slots are selected and events are moved and resized. Renders a <div>.
| Prop | Type | Default |
|---|---|---|
viewThe view. | SchedulerView | — |
defaultViewThe view when the component is initially rendered. | SchedulerView | 'week' |
onViewChangeEvent handler called when actionsRef.setView changes the view. | ((view: SchedulerView, eventDetails: { reason: "imperative-action"; event: Event; cancel: () => void; allowPropagation: () => void; isCanceled: boolean; isPropagationAllowed: boolean; trigger: Element | undefined; }) => void) | — |
dateThe anchor of the range shown (YYYY-MM-DD). | `${number}-${number}-${number}` | — |
defaultDateThe anchor of the range when the component is initially rendered. | `${number}-${number}-${number}` | — |
onDateChangeEvent handler called when the range moves: the buttons, Page Up and Page Down, actionsRef. | ((date: `${number}-${number}-${number}`, eventDetails: SchedulerRootDateChangeEventDetails) => void) | — |
eventsThe events, never written: a move or a resize is a request through onEventChange. | readonly SchedulerEvent<unknown>[] | [] |
onEventChangeEvent handler called once per gesture, at the drop, with the event's new and previous range. | ((change: SchedulerEventChange, eventDetails: SchedulerRootEventChangeEventDetails) => void) | — |
valueThe selected range of a new event. | SchedulerRange | null | — |
defaultValueThe selected range when the component is initially rendered. | SchedulerRange | null | null |
onValueChangeEvent handler called when a selection is committed. | ((value: SchedulerRange | null, eventDetails: SchedulerRootValueChangeEventDetails) => void) | — |
slotDurationThe minutes of a slot (a row of the time grid). | number | 30 |
snapDurationThe minutes of a drag step and of an arrow key while grabbed. | number | 15 |
largeStepThe minutes of Shift and an arrow key while grabbed. | number | 60 |
defaultEventDurationThe minutes a click or Enter on a slot selects. | number | 60 |
minTimeThe start of each day's visible window. | TimeString | '00:00' |
maxTimeThe end of each day's visible window; '24:00' is the end of the day. | TimeString | '24:00' |
scrollToTimeThe time the Grid scrolls to on mount. | TimeString | — |
agendaDaysThe days the agenda view lists from date. | number | 7 |
fixedWeeksWhether the month view always shows six weeks. | boolean | false |
localeThe locale of the texts and of the week. | string | 'en-US' |
firstDayOfWeekThe first day of the week, 0 is Sunday. | number | null | — |
isSlotUnavailableWhether a slot cannot be selected or dropped on; gets its start and exclusive end. | ((start: string, end: string) => boolean) | — |
readOnlyWhether nothing can be selected, moved or resized; navigation and onAction work. | boolean | false |
disabledWhether the scheduler ignores every interaction but moving focus. | boolean | false |
longPressDelayThe milliseconds a touch or pen holds an Event or a Slot before a gesture starts. | number | 250 |
todayToday (YYYY-MM-DD). | `${number}-${number}-${number}` | — |
nowNow (YYYY-MM-DDTHH:MM). | `${number}-${number}-${number}T${number}:${number}${string}` | — |
timeZoneThe IANA time zone that decides today and now when they are read. | string | — |
labelsThe texts of the parts and the status messages, in English by default. | Partial<SchedulerLabels> | — |
actionsRefA ref to imperative actions. | React.RefObject<SchedulerActions | null> | — |
RowHeaderScheduler.RowHeader
A row's header: the time of the hour's first slot, nothing on the hour's other rows, or the all-day row's “All day” in the GridHeader. Renders a <div>.
| Prop | Type | Default |
|---|---|---|
timeThe time of the hour's first slot, a TimeString; the hour's other rows leave it out. | string | — |
SlotScheduler.Slot
A cell of the grid: a timed slot (start), an all-day cell or a month cell (date). Renders a <div>.
| Prop | Type | Default |
|---|---|---|
startThe start of a timed slot, a DateTimeString. | string | — |
dateThe day of an all-day cell or a month cell, a DateString. | string | — |
TodayButtonScheduler.TodayButton
Moves the range to the one that holds today. Renders a <button>.
| Prop | Type | Default |
|---|---|---|
nativeButtonWhether the component renders a native <button> element when replacing it | boolean | true |
focusableWhenDisabledWhether the button stays focusable while the Root is disabled. | boolean | true |
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 to the same time on the previous or next day; swapped in right to left | Slot |
| ArrowUpArrowDown | Moves focus a slot; in the month view, a week; unavailable slots are skipped | Slot |
| HomeEnd | Moves focus to the first or last day at the same time; with Ctrl, to the first or last slot of the grid | Slot |
| PageUpPageDown | Shows the previous or next range at the same slot (onDateChange, reason keyboard) | Slot |
| EnterSpace | Selects defaultEventDuration from the focused slot, or commits a pending range | Slot |
| ShiftArrowDownShiftArrowUp | Extends or shrinks a pending range by a slot | Slot |
| Escape | Clears a pending range; not handled when there is none | Slot |
| Tab | Moves from the focused slot into its events and their handles, then out of the grid | Event |
| EnterSpace | Calls onAction; the arrows keep moving through the grid | Event |
| EnterSpace | Picks the event up (aria-pressed), and drops it on the second press | MoveHandle |
Move the event by snapDuration or a day, with Shift by largeStep; Enter drops it | MoveHandle | |
| ArrowUpArrowDown | Move the start or the end by snapDuration, never past the other edge | ResizeHandle |
| Escape | Cancels the move or the resize; nothing is called and focus stays on the handle | MoveHandle |
| Tab | Moves from event to event; the agenda has no arrow keys | Event |
Accessibility
| Part | Sets | So that |
|---|---|---|
| Root | role="group" named by the Heading; its last child is a visually hidden status region | the scheduler is one named region, and picks up, moves and drops are announced |
| Heading | h2, aria-live="polite" and aria-atomic, the range as text ("November 8 – 14, 2026") | a new range is announced once when the buttons or PageDown change it, and not on the first render |
| PrevButton | a button named from labels by the view ("Previous week", "Next month") when its content has no text | an icon-only button says where it goes |
| NextButton | as PrevButton ("Next week", "Next 7 days") | as PrevButton |
| TodayButton | as PrevButton, labels "Today" | as PrevButton |
| Grid | role="grid" named by the Heading (an aria-label without one), aria-readonly with readOnly | the grid is read by its range, and a read-only one says so |
| HeaderCell | role="columnheader", the short day as text and the full date as aria-label and abbr, aria-current="date" today | a column is read "Tuesday, November 10", not "Tue 10" |
| Slot | role="gridcell", roving tabindex, aria-label the date and time with the count of its events, aria-selected, aria-disabled when unavailable | the focused slot says when it is, whether it is chosen and whether it can be |
| Event | role="button", aria-label the title, times and date ("Standup, 9:30 AM to 10:00 AM, Tuesday, November 10, 2026"), a Tab stop only in the focused slot | an event is read with its time, and the grid stays one Tab stop |
| MoveHandle | a button named "Move event" and its event, aria-pressed while grabbed, touch-action: none | the drag has a keyboard alternative (WCAG 2.5.7) whose state is read |
| ResizeHandle | as MoveHandle, named "Resize start of event" or "Resize end of event" by edge | the start and the end can each be moved by keys |
| MoreButton | a button named "2 more events on Tuesday, November 10" | the hidden events of a month cell are counted with their date |
| Placeholder | aria-hidden="true" | the preview of a drag only shows; the status region speaks |
| NowIndicator | aria-hidden="true" | the line of now only shows |
| AgendaDay | role="group" named by its AgendaHeading (h3) | each day of the agenda is a named list of buttons |
Focus order
- The navigation buttons and the Heading, in the order you render them; the app's view switch where you put it.
- The grid is one Tab stop: the last focused slot, else today's slot holding now, else today's first visible slot, else the first slot. Nothing takes focus on mount.
Tabfrom the focused slot reaches its events, each followed by its handles, then leaves the grid. A drop leaves focus on the handle.- In the agenda every event is a Tab stop, in time order.